BEncode.php
<?php
class BEncode {
/**
* @var string
*/
protected
static $haystack = null; /**
* @var int
*/
/**
* Decode
*
* @param $haystack string
* @return mixed
*/
public static function decode
($haystack) {
if( !isset($haystack) ) return null;
self::$haystack = $haystack;
self::$offset = 0;
$result = self::parse();
self::$haystack = null;
return $result;
}
/**
*
* @return array
*/
protected
static function parseDictionarie
() {
self::$offset++;
{
$key = self::parseString();
if( strlen( $key ) < 1
) throw
new Exception
('Unknown key name at offset:'.self::$offset, 2
);
$result[ $key ] = self::parse();
}
self::$offset += 1;
return $result;
}
/**
*
* @return array
*/
protected
static function parseList
() {
self::$offset++;
{
$result[] = self::parse();
}
self::$offset += 1;
return $result;
}
/**
*
* @return int
*/
protected
static function parseIntiger
() {
self::$offset += 1;
$l = strpos(self::$haystack,'e', self::$offset) - self::$offset; $value = (int
)substr(self::$haystack, self::$offset, $l);
if( !is_numeric($value) ) throw
new Exception
('Parsed data is not integer ''.$value.'' at offset:'.self::$offset, 3
);
self::$offset += $l + 1;
return $value;
}
/**
*
* @return string
*/
protected
static function parseString
() {
$string = '';
$l = strpos(self::$haystack,':', self::$offset) - self::$offset; $length = (int
)substr(self::$haystack, self::$offset, $l); self::$offset += $l+1;
$string = (string
)substr(self::$haystack, self::$offset, $length); self::$offset += $length;
if( !isset($string) ) $string = '';
return $string;
}
/**
*
* @return mixed
*/
protected
static function parse
() {
$result = null;
if( self::end() ) return null;
switch( self::$haystack[self::$offset] )
{
case 'd':
$result = self::parseDictionarie();
break;
case 'l':
$result = self::parseList();
break;
case 'i':
$result = self::parseIntiger();
break;
default:
if( is_numeric( self::$haystack[self::$offset] ) ) $result = self::parseString();
else
throw new Exception('Unknown type. ''.self::$haystack[self::$offset].'' at offset:'.self::$offset, 1);
}
return $result;
}
{
if( @!isset( self::$haystack[self::$offset] ) ) return true; if( self::$haystack[self::$offset] == 'e' ) return true;
return false;
}
/* ENCODE */
/**
* Encode
* @param $mixed mixed
* @return string
*/
public static function encode
($mixed) {
if( !isset($mixed) ) return '';
$result = '';
{
if( count($mixed) == 0
) $type = 'd'; // empty array should be dictionarie else
{
$type = 'l';
// find what type of array we have
// if there's one(or more) none numeric key we have dictionarie
foreach($mixed as $key => $e)
{
{
$type = 'd';
break;
}
}
}
$result .= $type;
foreach($mixed as $key => $e)
{
if( $type == 'l' )
$result .= self::encode($e);
else
{
$result .= (int
)strlen($key).':'.$key.self::encode($e); }
}
$result .= 'e';
}
else
{
$result .= 'i'.(int)$mixed.'e';
else
{
$result .= (int
)strlen($mixed).':'.$mixed; }
}
return $result;
}
}
?>
Clasa Torrent
<?php
require 'BEncode.php';
/**
* hex2bin
*
* from php.net
*
* @param $h string
* @return string
*/
function hex2bin($h)
{
if( strlen($h)%2
!= 0
) return null; $r='';
for( $a=0; $a<strlen
($h); $a+=2
) { $r.=chr
(hexdec($h{$a}.$h{($a+1
)})); } return $r;
}
class Tracker {
/**
* Tracker url
* @var string
*/
protected $url;
public function __construct($url)
{
$this->url = $url;
}
/**
* Get data from tracker
*
* @param $hash_info string
* @return mixed false if failed
*/
public function ask( $hash_info )
{
$url = $this->url
.'?info_hash='. urlencode(hex2bin
( $hash_info )) .'&peer_id='. self::getPeerId()
.'&port=0&uploaded=0&downloaded=0&left=100';
if( !isset( $result ) && empty($result) ) return false;
try {
$result = BEncode::decode($result);
}
catch(Exception $e)
{
return false;
}
return $result;
}
/**
* Validate the url
* @param $url string
* @return bool
*/
public static function validUrl
($url) {
return preg_match('|^http(s)?://[a-z0-9-]+(.[a-z0-9-]+)*(:[0-9]+)?(/.*)|i', $url); }
/**
* PeerID
* @var string
*/
public static function getPeerId
() {
if( self::$sId === null )
{
}
return self::$sId;
}
}
class Torrent {
/**
* Torrent data
* @var array
*/
protected $data;
/**
* Hash (sha1)
* @var string
*/
protected $hash;
/**
* @var int
*/
protected $peers = -1;
/**
* @var int
*/
protected $seeds = -1;
/**
* Constructor
* @param $data string
*/
public function __construct($data)
{
$this->data = BEncode::decode($data);
$this->hash = sha1( BEncode::encode($this->data['info']) );
}
/**
*
* @return string
*/
public function getName()
{
return @$this->data['info']['name'];
}
/**
*
* @return array
*/
public function getFiles()
{
return @$this->data['info']['files'];
}
public function getHash()
{
return $this->hash;
}
public function getSeeds()
{
return $this->seeds;
}
public function getPeers()
{
return $this->peers;
}
/**
* Update tracker info
* @return bool
*/
public function updateTracker()
{
if( !is_array($this->data) ) return false;
if( @Tracker::validUrl($this->data['announce']) )
$trackers[] = $this->data['announce'];
if( is_array($this->data['announce-list']) ) {
foreach( $this->data['announce-list'] as $ar )
{
}
}
foreach( $trackers as $url )
{
if( @!Tracker::validUrl($url) ) continue;
$t = new Tracker($url);
$result = $t->ask( $this->getHash() );
if( !is_array($result) || isset($result['failure reason']) ) continue
;
if( isset( $result['complete'] ) ) $this->seeds = (int
)$result['complete']; if( isset( $result['incomplete'] ) ) $this->peers = (int
)$result['incomplete'];
return true;
}
return false;
}
/**
* Create from torrent file
*
* @param $filename string
* @return Torrent
*/
public static function fromFile
($filename) {
return new Torrent($content);
}
}
?>
Klasa pobiera dane o torrencie(ilość seedów peerów) z trackera, niestety nie każdy tracker wysyła 'complete' & 'incomplete'
przykład:
<?php
$torrent = Torrent::fromFile('jakis.torrent');
$torrent->updateTracker();
var_dump( $torrent->getSeeds(), $torrent->getPeers() ); ?>