phputorrent

µTorrent - Scraping


I have some troubleshoot with µTorrent and I think you can help me.

My µTorrent doesn't receive any data about seeds and leeches

enter image description here

Here is my scrapement code:

<?php
class scrapement extends Core {
        public function renderPage() {
                if (!isset($_GET['info_hash']) || (strlen($_GET['info_hash']) != 20))
                        $this->error('Invalid hash');

                $query = $this->query("SELECT `info_hash`, `seeders`, `leechers`, `times_completed` FROM `torrents` WHERE `info_hash` = '".$this->checkValues($_GET['info_hash'], 0)."'");

                if(!mysql_num_rows($query)) {
                        $this->error('No torrent with that hash found');
                }

                $benc = "d5:files";
                while ($row = $this->fetch($query))
                {
                        $benc .= "d20:".str_pad($row["info_hash"], 20)."d8:completei".$row['seeders']."e10:downloadedi".$row['times_completed']."e10:incompletei".$row['leechers']."eee";
                }
                $benc .= 'ed5:flagsd20:min_request_intervali1800eee';

                $this->getLog($benc);

                header("Content-Type: text/plain");
                header("Pragma: no-cache");
                echo $benc;
        }

        private function error($err) {
                header('Content-Type: text/plain; charset=UTF-8');
                header('Pragma: no-cache');
                exit("d14:failure reason".strlen($err).":{$err}ed5:flagsd20:min_request_intervali1800eeee");
        }
}
?>

The logs from scrapement script: ($this->getLog($benc);)

d5:filesd20:êzo¦G{9…NÑ´ò43d8:completei1e10:downloadedi21e10:incompletei0eeeed5:flagsd20:min_request_intervali1800eee

as µTorrent has not any logs to view I tried with Ratio Master ...and... the scrapement it's working

[02:49:10] GET /scrapement?passkey=fe4d2xxxxxx&info_hash=B%c3%2c%e7%be%ec%2a%5c%a1%c4c%f8%c4M35%3f%f3%c6%e8 HTTP/1.1
Host: 
User-Agent: uTorrent/1800
Accept-Encoding: gzip

enter image description here

so why the uTorrent doesn't receive any data?


Solution

  • You have an error in your bencoded data format. I recommend you use a proper encoding routine to convert data structures to bencoded format, rather than manually constructing the strings by concatenation. Here is a class which handles both encoding and decoding:

    /**
     * Encodes and decodes bencode formatted strings
     */
    class BEncoder
    {
        /**
         * Encode a value using the bencode format
         *
         * @param mixed $data The value to encode
         *
         * @return string The encoded value
         *
         * @throws \InvalidArgumentException When an unencodable value is encountered
         */
        public function encode($data)
        {
            if (is_resource($data)) {
                throw new \InvalidArgumentException('Resources cannot be bencoded');
            }
    
            // convert objects to arrays of public properties
            // this makes it possible to sort dictionaries as required
            if (is_object($data)) {
                $data = get_object_vars($data);
            }
    
            if (is_string($data)) {
                // byte sequence
                $result = strlen($data) . ':' . $data;
            } else if (is_int($data) || is_float($data) || is_bool($data)) {
                // integer
                $result = 'i' . round($data, 0) . 'e';
            } else if (is_array($data)) {
                if (array_values($data) === $data) {
                    // list
                    $result = 'l';
                    foreach ($data as $value) {
                        $result .= $this->encode($value);
                    }
                } else if (is_array($data)) {
                    // dictionary
                    ksort($data);
                    $result = 'd';
                    foreach ($data as $key => $value) {
                        $result .= $this->encode((string) $key) . $this->encode($value);
                    }
                }
                $result .= 'e';
            }
    
            return $result;
        }
    
        /**
         * Decode a value using the bencode format
         *
         * @param string $data The value to decode
         *
         * @return mixed The decoded value
         *
         * @throws \InvalidArgumentException When an undecodable value is encountered
         */
        public function decode($data)
        {
            if (!is_string($data)) {
                throw new \InvalidArgumentException('Data is not a valid bencoded string');
            }
            $data = trim($data);
    
            try {
                $result = $this->decodeComponent($data, $position);
    
                if ($data !== '') {
                    throw new \InvalidArgumentException('Data found after end of value');
                }
            } catch (\InvalidArgumentException $e) {
                $err = 'Syntax error at character ' . $position . ' "' . substr($data, 0, 7) . '..."): ' . $e->getMessage();
                throw new \InvalidArgumentException($err);
            }
    
            return $result;
        }
    
        /**
         * Move the pointer in the data currently being decoded
         *
         * @param string $data     The data being decoded
         * @param int    $position The position pointer
         * @param int    $count    The number of bytes to move the pointer
         */
        private function movePointer(&$data, &$position, $count)
        {
            $data = (string) substr($data, $count);
            $position += $count;
        }
    
        /**
         * Recursively decode a structure from a data string
         *
         * @param string $data     The data being decoded
         * @param int    $position The position pointer
         *
         * @return mixed The decoded value
         *
         * @throws \InvalidArgumentException When an undecodable value is encountered
         */
        private function decodeComponent(&$data, &$position = 0)
        {
            switch ($data[0]) {
                case 'i':
                    if (!preg_match('/^i(-?\d+)e/', $data, $matches)) {
                        throw new \InvalidArgumentException('Invalid integer');
                    }
    
                    $this->movePointer($data, $position, strlen($matches[0]));
                    return (int) $matches[1];
    
                case 'l':
                    $this->movePointer($data, $position, 1);
                    if ($data === '') {
                        throw new \InvalidArgumentException('Unexpected end of list');
                    }
    
                    $result = array();
                    while ($data[0] !== 'e') {
                        $value = $this->decodeComponent($data, $position);
                        if ($data === '') {
                            throw new \InvalidArgumentException('Unexpected end of list');
                        }
    
                        $result[] = $value;
                    }
    
                    $this->movePointer($data, $position, 1);
                    return $result;
    
                case 'd':
                    $this->movePointer($data, $position, 1);
                    if ($data === '') {
                        throw new \InvalidArgumentException('Unexpected end of dictionary');
                    }
    
                    $result = array();
                    while ($data[0] !== 'e') {
                        $key = $this->decodeComponent($data, $position);
                        if ($data === '') {
                            throw new \InvalidArgumentException('Unexpected end of dictionary');
                        }
                        $value = $this->decodeComponent($data, $position);
                        if ($data === '') {
                            throw new \InvalidArgumentException('Unexpected end of dictionary');
                        }
    
                        $result[$key] = $value;
                    }
    
                    $this->movePointer($data, $position, 1);
                    return $result;
    
                default:
                    if (!preg_match('/^(\d+):/', $data, $matches)) {
                        throw new \InvalidArgumentException('Unknown data type');
                    }
    
                    $this->movePointer($data, $position, strlen($matches[0]));
                    if (strlen($data) < $matches[1]) {
                        $this->movePointer($data, $position, strlen($data));
                        throw new \InvalidArgumentException('Unexpected end of byte string');
                    }
    
                    $result = substr($data, 0, $matches[1]);
                    $this->movePointer($data, $position, $matches[1]);
    
                    return $result;
            }
        }
    }
    

    To use it with your code you would do something like this:

    class scrapement extends Core
    {
        private $bencoder;
    
        public function __construct($bencoder)
        {
            // inject an instance of the BEncoder class into this object
            $this->bencoder = $bencoder;
        }
    
        public function renderPage()
        {
            if (!isset($_GET['info_hash']) || (strlen($_GET['info_hash']) != 20)) {
                $this->error('Invalid hash');
            }
    
            $query = $this->query("
                SELECT `info_hash`, `seeders`, `leechers`, `times_completed`
                FROM `torrents`
                WHERE `info_hash` = '" . $this->checkValues($_GET['info_hash'], 0) . "'
            ");
            if (!mysql_num_rows($query)) {
                $this->error('No torrent with that hash found');
            }
    
            $data = array(
                'flags' => array(
                    'min_request_interval' => 1800
                ),
                'files' => array()
            );
    
            while ($row = $this->fetch($query)) {
                $hash = str_pad($row["info_hash"], 20);
                $data['files'][$hash] = array(
                    'complete'   => $row['seeders'],
                    'incomplete' => $row['leechers'],
                    'downloaded' => $row['times_completed']
                );
            }
    
            $data = $this->bencoder->encode($data);
            $this->getLog($data);
    
            header("Content-Type: text/plain");
            header("Pragma: no-cache");
            echo $data;
        }
    
        private function error($err)
        {
            $data = array(
                'flags' => array(
                    'min_request_interval' => 1800
                ),
                'failure reason' => $err
            );
    
            $data = $this->bencoder->encode($data);
            $this->getLog($data);
    
            header('Content-Type: text/plain; charset=UTF-8');
            header('Pragma: no-cache');
            echo $data;
        }
    }