BitTorrent v2 support 🐸 (#866)

* BitTorrent v2 support

* Update TorrentFileList.php

* BitTorrent v2 support

Added support for BitTorrent v2 file hash displaying, magnet links

* Updated

* Updated

* Update changes.txt

* Update Torrent.php

* Update Torrent.php

* Updated

* Added support in announcer

* Update announce.php

* Update scrape.php

* Update scrape.php

* Update scrape.php

* Changed the condition for single files

* Update scrape.php

* Update displaying_torrent.php

* Update displaying_torrent.php

* Update displaying_torrent.php

---------

Co-authored-by: Roman Kelesidis <roman25052006.kelesh@gmail.com>
This commit is contained in:
Constantine Kovalensky 2023-08-27 15:33:38 +04:00 committed by GitHub
commit fadce7a297
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 104 additions and 27 deletions

View file

@ -63,12 +63,22 @@ $passkey = ${$passkey_key} ?? null;
// Verify request
// Required params (info_hash, peer_id, port, uploaded, downloaded, left, passkey)
if (!isset($info_hash) || strlen($info_hash) != 20) {
msg_die('Invalid info_hash');
if (!isset($info_hash)) {
msg_die('info_hash does not exists');
}
if (!isset($peer_id) || strlen($peer_id) != 20) {
msg_die('Invalid peer_id');
}
// Check info_hash version
if (strlen($info_hash) == 32) {
$is_bt_v2 = true;
} elseif (strlen($info_hash) == 20) {
$is_bt_v2 = false;
} else {
msg_die('Invalid info_hash');
}
if (!isset($port) || $port < 0 || $port > 0xFFFF) {
msg_die('Invalid port');
}
@ -178,13 +188,14 @@ if ($lp_info) {
} else {
// Verify if torrent registered on tracker and user authorized
$info_hash_sql = rtrim(DB()->escape($info_hash), ' ');
$info_hash_where = $is_bt_v2 ? "WHERE tor.info_hash_v2 = '$info_hash_sql'" : "WHERE tor.info_hash = '$info_hash_sql'";
$passkey_sql = DB()->escape($passkey);
$sql = "
SELECT tor.topic_id, tor.poster_id, tor.tor_type, u.*
FROM " . BB_BT_TORRENTS . " tor
LEFT JOIN " . BB_BT_USERS . " u ON u.auth_key = '$passkey_sql'
WHERE tor.info_hash = '$info_hash_sql'
$info_hash_where
LIMIT 1
";

View file

@ -22,11 +22,21 @@ if (isset($_GET['?info_hash']) && !isset($_GET['info_hash'])) {
$_GET['info_hash'] = $_GET['?info_hash'];
}
if (!isset($_GET['info_hash']) || strlen($_GET['info_hash']) != 20) {
msg_die('Invalid info_hash');
$is_bt_v2 = null;
$info_hash = isset($_GET['info_hash']) ? (string)$_GET['info_hash'] : null;
if (!isset($info_hash)) {
msg_die('info_hash does not exists');
}
$info_hash = $_GET['info_hash'];
// Check info_hash version
if (strlen($info_hash) == 32) {
$is_bt_v2 = true;
} elseif (strlen($info_hash) == 20) {
$is_bt_v2 = false;
} else {
msg_die('Invalid info_hash');
}
function msg_die($msg)
{
@ -42,15 +52,20 @@ function msg_die($msg)
require __DIR__ . '/includes/init_tr.php';
$info_hash_sql = rtrim(DB()->escape($info_hash), ' ');
$info_hash_where = $is_bt_v2 ? "WHERE tor.info_hash_v2 = '$info_hash_sql'" : "WHERE tor.info_hash = '$info_hash_sql'";
$row = DB()->fetch_row("
SELECT 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 tor.info_hash = '$info_hash_sql'
$info_hash_where
LIMIT 1
");
if (!$row) {
msg_die('Torrent not registered, info_hash = ' . bin2hex($info_hash_sql));
}
$output['files'][$info_hash] = [
'complete' => (int)$row['seeders'],
'downloaded' => (int)$row['complete_count'],

View file

@ -274,6 +274,7 @@ DROP TABLE IF EXISTS `bb_bt_torrents`;
CREATE TABLE IF NOT EXISTS `bb_bt_torrents`
(
`info_hash` VARBINARY(20) NOT NULL DEFAULT '',
`info_hash_v2` VARBINARY(32) NOT NULL DEFAULT '',
`post_id` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',
`poster_id` MEDIUMINT(9) NOT NULL DEFAULT '0',
`topic_id` MEDIUMINT(8) UNSIGNED NOT NULL DEFAULT '0',

View file

@ -67,3 +67,4 @@ INSERT INTO `bb_cron` (`cron_active`, `cron_title`, `cron_script`, `schedule`, `
`disable_board`, `run_counter`) VALUES ('1', 'PM cleanup', 'clean_pm.php', 'daily', '', '05:00:00', '70', '', '', '', '1', '', '0', '1', '0');
ALTER TABLE `bb_posts_text` CHANGE `post_text` `post_text` MEDIUMTEXT NOT NULL;
ALTER TABLE `bb_privmsgs_text` CHANGE `privmsgs_text` `privmsgs_text` MEDIUMTEXT NOT NULL;
ALTER TABLE `bb_bt_torrents` ADD COLUMN `info_hash_v2` VARBINARY(32) NOT NULL DEFAULT '';

View file

@ -155,7 +155,7 @@ if ($tor_reged && $tor_info) {
$tor_type = $tor_info['tor_type'];
// Magnet link
$tor_magnet = create_magnet($tor_info['info_hash'], $user_passkey);
$tor_magnet = create_magnet($tor_info['info_hash'], $tor_info['info_hash_v2'], $user_passkey);
// ratio limits
$min_ratio_dl = $bb_cfg['bt_min_ratio_allow_dl_tor'];

View file

@ -1760,11 +1760,12 @@ function decode_text_match($txt)
* Create magnet link
*
* @param string $infohash
* @param string $infohash_v2
* @param string|bool $auth_key
*
* @return string
*/
function create_magnet($infohash, $auth_key): string
function create_magnet(string $infohash, string $infohash_v2, $auth_key): string
{
global $bb_cfg, $images;
@ -1778,7 +1779,7 @@ function create_magnet($infohash, $auth_key): string
return false;
}
return '<a href="magnet:?xt=urn:btih:' . bin2hex($infohash) . '&tr=' . urlencode($bb_cfg['bt_announce_url'] . "?{$bb_cfg['passkey_key']}=$auth_key") . '"><img src="' . $images['icon_magnet'] . '" width="12" height="12" border="0" /></a>';
return '<a href="magnet:?xt=urn:btih:' . bin2hex($infohash) . (!empty($infohash_v2) ? '&xt=urn:btmh:1220' . bin2hex($infohash_v2) : '') . '&tr=' . urlencode($bb_cfg['bt_announce_url'] . "?{$bb_cfg['passkey_key']}=$auth_key") . '"><img src="' . $images['icon_magnet'] . '" width="12" height="12" border="0" /></a>';
}
function set_die_append_msg($forum_id = null, $topic_id = null, $group_id = null)

View file

@ -287,7 +287,8 @@ class Torrent
$topic_id = $torrent['topic_id'];
$forum_id = $torrent['forum_id'];
$poster_id = $torrent['poster_id'];
$info_hash = null;
$info_hash = $info_hash_v2 = null;
$info_hash_sql = $info_hash_v2_sql = null;
if ($torrent['extension'] !== TORRENT_EXT) {
return self::torrent_error_exit($lang['NOT_TORRENT']);
@ -345,10 +346,23 @@ class Torrent
return self::torrent_error_exit($lang['TORFILE_INVALID']);
}
// Check if torrent contains info_hash v2
$bt_v2 = false;
if (($info['meta version'] ?? null) == 2 && is_array($info['file tree'] ?? null)) {
$bt_v2 = true;
}
// Getting info_hash v1
$info_hash = pack('H*', sha1(\SandFox\Bencode\Bencode::encode($info)));
$info_hash_sql = rtrim(DB()->escape($info_hash), ' ');
$info_hash_md5 = md5($info_hash);
// Getting info_hash v2
if ($bt_v2) {
$info_hash_v2 = pack('H*', hash('sha256', \SandFox\Bencode\Bencode::encode($info)));
$info_hash_v2_sql = rtrim(DB()->escape($info_hash_v2), ' ');
}
// Ocelot
if ($bb_cfg['ocelot']['enabled']) {
self::ocelot_update_tracker('add_torrent', ['info_hash' => rawurlencode($info_hash), 'id' => $topic_id, 'freetorrent' => 0]);
@ -366,7 +380,10 @@ class Torrent
$totallen = (float)$info['length'];
} elseif (isset($info['files']) && \is_array($info['files'])) {
foreach ($info['files'] as $fn => $f) {
$totallen += (float)$f['length'];
// Exclude padding files
if (($f['attr'] ?? null) !== 'p') {
$totallen += (float)$f['length'];
}
}
} else {
return self::torrent_error_exit($lang['TORFILE_INVALID']);
@ -374,8 +391,8 @@ class Torrent
$size = sprintf('%.0f', (float)$totallen);
$columns = ' info_hash, post_id, poster_id, topic_id, forum_id, attach_id, size, reg_time, tor_status';
$values = "'$info_hash_sql', $post_id, $poster_id, $topic_id, $forum_id, $attach_id, '$size', $reg_time, $tor_status";
$columns = 'info_hash, info_hash_v2, post_id, poster_id, topic_id, forum_id, attach_id, size, reg_time, tor_status';
$values = "'$info_hash_sql', '$info_hash_v2_sql', $post_id, $poster_id, $topic_id, $forum_id, $attach_id, '$size', $reg_time, $tor_status";
$sql = "INSERT INTO " . BB_BT_TORRENTS . " ($columns) VALUES ($values)";

View file

@ -43,18 +43,45 @@ class TorrentFileList
{
global $html;
$this->build_filelist_array();
if (($this->tor_decoded['info']['meta version'] ?? null) == 2 && is_array($this->tor_decoded['info']['file tree'] ?? null)) {
// v2
function fileTree($array, $name = '')
{
$folders = [];
$rootFiles = [];
if ($this->multiple) {
if ($this->files_ary['/'] !== '') {
$this->files_ary = array_merge($this->files_ary, $this->files_ary['/']);
unset($this->files_ary['/']);
foreach ($array as $key => $value) {
if (is_array($value) && !isset($value[''])) {
$html_v2 = fileTree($value);
$folders[] = "<li><span class=\"b\">$key</span><ul>$html_v2</ul></li>";
} else {
$length = $value['']['length'];
$root = bin2hex($value['']['pieces root'] ?? '');
$rootFiles[] = "<li><span>$key<i>$length</i> <h style='color:gray;'>$root</h></span></li>";
}
}
$allItems = array_merge($folders, $rootFiles);
return '<div class="tor-root-dir">' . (empty($folders) ? '' : $name) . '</div><ul class="tree-root">' . implode('', $allItems) . '</ul>';
}
$filelist = $html->array2html($this->files_ary);
return "<div class=\"tor-root-dir\">{$this->root_dir}</div>$filelist";
}
return implode('', $this->files_ary['/']);
return fileTree($this->tor_decoded['info']['file tree'], $this->tor_decoded['info']['name']);
} else {
// v1
$this->build_filelist_array();
if ($this->multiple) {
if ($this->files_ary['/'] !== '') {
$this->files_ary = array_merge($this->files_ary, $this->files_ary['/']);
unset($this->files_ary['/']);
}
$filelist = $html->array2html($this->files_ary);
return "<div class=\"tor-root-dir\">{$this->root_dir}</div>$filelist";
}
return implode('', $this->files_ary['/']);
}
}
/**
@ -81,6 +108,10 @@ class TorrentFileList
if (!isset($f['path']) || !\is_array($f['path'])) {
continue;
}
// Exclude padding files
if (($f['attr'] ?? null) === 'p') {
continue;
}
array_deep($f['path'], 'clean_tor_dirname');
$length = isset($f['length']) ? (float)$f['length'] : 0;

View file

@ -645,7 +645,7 @@ if ($allowed_forums) {
$select = "
SELECT
tor.topic_id, tor.post_id, tor.attach_id, tor.size, tor.reg_time, tor.complete_count, tor.seeder_last_seen, tor.tor_status, tor.tor_type,
t.topic_title, t.topic_time, t.topic_replies, t.topic_views, sn.seeders, sn.leechers, tor.info_hash
t.topic_title, t.topic_time, t.topic_replies, t.topic_views, sn.seeders, sn.leechers, tor.info_hash, tor.info_hash_v2
";
$select .= (!$hide_speed) ? ", sn.speed_up, sn.speed_down" : '';
$select .= (!$hide_forum) ? ", tor.forum_id" : '';
@ -702,7 +702,7 @@ if ($allowed_forums) {
$s_last = $tor['seeder_last_seen'];
$att_id = $tor['attach_id'];
$size = $tor['size'];
$tor_magnet = create_magnet($tor['info_hash'], \TorrentPier\Legacy\Torrent::getPasskey($user_id));
$tor_magnet = create_magnet($tor['info_hash'], $tor['info_hash_v2'], \TorrentPier\Legacy\Torrent::getPasskey($user_id));
$compl = $tor['complete_count'];
$dl_sp = ($dl) ? humn_size($dl, 0, 'KB') . '/s' : '0 KB/s';
$ul_sp = ($ul) ? humn_size($ul, 0, 'KB') . '/s' : '0 KB/s';

View file

@ -285,7 +285,7 @@ if ($forum_data['allow_reg_tracker']) {
}
$select_tor_sql = ',
bt.auth_key, tor.info_hash, tor.size AS tor_size, tor.reg_time, tor.complete_count, tor.seeder_last_seen, tor.attach_id, tor.tor_status, tor.tor_type,
bt.auth_key, tor.info_hash, tor.info_hash_v2, tor.size AS tor_size, tor.reg_time, tor.complete_count, tor.seeder_last_seen, tor.attach_id, tor.tor_status, tor.tor_type,
sn.seeders, sn.leechers
';
$select_tor_sql .= ($join_dl) ? ', dl.user_status AS dl_status' : '';
@ -470,7 +470,7 @@ foreach ($topic_rowset as $topic) {
));
if (isset($topic['tor_size'])) {
$tor_magnet = create_magnet($topic['info_hash'], ($topic['auth_key'] ?? false));
$tor_magnet = create_magnet($topic['info_hash'], $topic['info_hash_v2'], ($topic['auth_key'] ?? false));
$template->assign_block_vars('t.tor', array(
'SEEDERS' => (int)$topic['seeders'],