Делаем по быстрому битторрент-трекер на основе XBT и IPB

   Это статья - описание как я быстренько сделал битторрент-трекер с миниммум необходимых функций
 
   Итак, как всегда - исходные условия задачи. 
   Имеем сервер (на самом деле даже два см. "Зеркалируем форумы по взрослому") с инвижиновскими форумами.
   Недавно проапгрейженными до версии 2.2.2 (русская).
   Сразу предупреждаю, что вообще-то я форумом самим никогда особенно не занимался, да и не занимаюсь до сих пор, 
   но тут пришлось немного поковырятся в его внутренностях :).

   Для начала надо поднять backend трекера - т.е. именно серверную часть.
   Выбран был в этом качестве XBT - http://xbtt.sourceforge.net/
   Процедура инсталяции там описана, скажу лишь что поскольку у меня была Slackware - мне пришлось скачать библиотеку libboost вручную.
   libboost можно найти здесь - http://www.boost.org/

   Скачал, стандартно комбинация из трех волшебных команд configure, make, make install - ничего необычного
   Затем по инструкции с http://xbtt.sourceforge.net/tracker/ забрал и собрал XBT Tracker:

   svn co https://xbtt.svn.sourceforge.net/svnroot/xbtt/trunk/xbt/misc xbt/misc
   svn co https://xbtt.svn.sourceforge.net/svnroot/xbtt/trunk/xbt/Tracker xbt/Tracker
   cd xbt/Tracker
   ./make.sh

   Тут тоже ничего необычного, все собралось, имеем в результате исполняемый файл xbt_tracker

   Перед запуском серверной части сначала необходимо еще создать нужные таблицы.
   Я немного модифицировал скрипт входящий в комплект - кое-где добавил дополнительные поля, плюс добавил триггеров.
   Здесь его можно скачать отдельно, либо сразу со всеми файлами в архиве
   Создать таблицы я решил для простоты в той же БД что и сами форумы.

   Почему использовал триггеры? От лени - ситуация когда добавляется новый пользователь в форумах - для того, 
   чтобы он мог работать мне проще показалось решить не ковырянием кода форума - а просто навешиванием простейшего триггера.
   Обработать ситуацию удаления торрент-файла - тоже показалось проще через тригеры.

   Все, теперь можно и запустить xbt_tracker.
   Рисуем ему в конфиге логины и пароли для доступа к нужным ему таблицам (а нужны ему только таблицы xbt_* - поэтому,
   можно и даже нужно, создать отдельного mysql пользователя имеющего доступ _только_ к этим таблицам)
   конфиг xbt_tracker.conf получился примерно таким

//--------------------------
   mysql_host = ip
   mysql_user = user
   mysql_password = pass
   mysql_database = ipb_db
   announce_interval = 1800
   anonymous_connect = 0
   anonymous_announce = 0
   anonymous_scrape = 0
   auto_register = 0
   clean_up_interval = 60
   listen_check = 0
   listen_ipa = *
   listen_port = 2710
   log_access = 0
   log_announce = 0
   log_scrape = 0
   pid_file =
   read_config_interval = 300
   read_db_interval = 60
   redirect_url =
   scrape_interval = 60
   update_files_method = 2
   write_db_interval = 60
   debug = 0
//--------------------------

   Запускаем. Кстати, должен сказать - сам xbt_tracker у меня запущен на совсем другом сервере, отличном от того,
   на котором находится инвижновский форум и его БД.

   Теперь начинаем поднимать frontend - т.е. править исходники инвижновского форума чтобы можно было работать с трекером :).
   Все нижесказанное для форумов версии 2.2.2, русской версии.

   Для начала заходим в админскую часть и прописываем там разрещение для прикрепления файлов с расширением ".torrent"
   Закладка "Управление", раздел "Прикрепляемые файлы".

   Затем не выходя из админки надо сделать изменения для интерфейса.
   Закладка "Внешний вид", в каждом используемом стиле выбираем "изменить HTML-шаблоны".
   Находим skin_topic, там находим Show_attachments
   В самом конце добавляем код из этого файла (надо скачать его к себе и открыть в любом текстовом редакторе 
   - при открытии в интернет-эксплорер он покажет пустоту ;) )

   Аналогично надо найти skin_profile, там найти personal_portal_main и добавить код из этого файла
   Код лучше всего мне кажется добавить надо после раздела <!-- / Custom Fields --> и перед разделом <!-- Statistics -->  

   Все, в админке закончили, теперь надо править сам код инвижна
   Если вы скачаете архив со всеми файлами есть очень большая вероятность того что вам подойдут патчи, 
   и все будет очень просто - надо будет просто их применить к двум файлам: class_attach.php из каталога sources/classes/attach и
   profile.php из каталога sources/action_public

   Если нет - то пытаемся ручками все сделать :)
   Для начала необходимо скопировать библиотеку benc.php для работы с торрент-файлами в каталог /sources форума.
   Взять ее можно из этого архива, либо здесь

   В каталоге sources/classes/attach находим файл class_attach.php
   Находим функцию show_attachment (примерно 209 строка)
   Находим блок кода (пр. 298 строка)

    //-----------------------------------------
    // Open and display the file..
    //-----------------------------------------

    //$contents = file_get_contents( $file );

    header( "Content-Type: ".$this->ipsclass->cache['attachtypes'][ $attach['attach_ext'] ]['atype_mimetype'] );
    header( "Content-Disposition: inline; filename=\"".$attach['attach_file']."\"" );
    header( "Content-Length: ".(string)(filesize( $file ) ) );

    //print $contents;
    readfile( $file );
    exit();

   Заменяем на свой

    //-----------------------------------------
    // Open and display the file..
    //-----------------------------------------

    //$contents = file_get_contents( $file );

    if(strcasecmp($attach['attach_ext'], "torrent"))
    {
      header( "Content-Type: ".$this->ipsclass->cache['attachtypes'][ $attach['attach_ext'] ]['atype_mimetype'] );
      header( "Content-Disposition: inline; filename=\"".$attach['attach_file']."\"" );
      header( "Content-Length: ".(string)(filesize( $file ) ) );
       
      readfile( $file );
      exit();
    }
    else
    {
      $ibz_sql = $this->ipsclass->DB->query("SELECT torrent_pass FROM xbt_users WHERE uid ='".addslashes($this->ipsclass->member['id'])."' LIMIT 1");
      $this->ipsclass->DB->simple_exec();
      $r = $this->ipsclass->DB->fetch_row($ibz_sql);

      header( "Content-Type: ".$this->ipsclass->cache['attachtypes'][ $attach['attach_ext']]['atype_mimetype'] );
      header( "Content-Disposition: inline; filename=\"".$attach['attach_file']."\"" );

      require_once( ROOT_PATH."sources/benc.php" );
      $torrent = bdec_file($file, 1 << 20);

// Для отключения DHT
//    $torrent["value"]["info"]["value"]["private"]["value"] = 1;

// 172.20.0.4 замените на свой ип-адрес
      $torrent["value"]["announce"]["value"] = "http://172.20.0.4:2710/".$r['torrent_pass']."/announce";

      header( "Content-Length: ".(string)(strlen( benc($torrent) ) ) );
      print benc($torrent);
      exit();
    }


   Находим функцию render_attachments (приимерно 340-я строка оригинального файла)
   Находим блок (примерно 520-я строка оригинального файла)

    // Full attachment thingy
    //-----------------------------------------

    $tmp = $this->ipsclass->compiled_templates[ $skin_name ]->Show_attachments( array (
            'attach_hits'  => $row['attach_hits'],
            'mime_image'   => $this->ipsclass->cache['attachtypes'][ $row['attach_ext'] ]['atype_img'],
	    'attach_file'  => $row['attach_file'],
            'attach_id'    => $row['attach_id'],
            'type'         => $this->type,
            'file_size'    => $this->ipsclass->size_format( $row['attach_filesize'] ),
         )       );

   И заменяем на свой код

    if($row['attach_ext']=="torrent")
    {
        $ibz_sql = $this->ipsclass->DB->query("SELECT info_hash, leechers, seeders,completed, files_size, files_count FROM xbt_files WHERE attach_id = '".$row['attach_id']."' LIMIT 1");
        $this->ipsclass->DB->simple_exec();
        $r = $this->ipsclass->DB->fetch_row($ibz_sql);
        $tmp = $this->ipsclass->compiled_templates[ $skin_name ]->Show_attachments(array (
            'attach_hits'  => $row['attach_hits'],
            'mime_image'   => $this->ipsclass->cache['attachtypes'][ $row['attach_ext'] ]['atype_img'],
            'attach_file'  => $row['attach_file'],
            'attach_id'    => $row['attach_id'],
            'type'         => $this->type,
            'file_size'    => $this->ipsclass->size_format( $row['attach_filesize'] ),
            'is_torrent'   => 1,
            'seeders'      => (int)$r['seeders'],
            'leechers'     => (int)$r['leechers'],
            'completed'    => (int)$r['completed'],
            'torrent_files_size' => $this->ipsclass->size_format( $r['files_size'] ),
            'torrent_files_count' => (int)$r['files_count'],
            'info_hash' => bin2hex($r["info_hash"]),
         )  );
    }
    else
    {
        $tmp = $this->ipsclass->compiled_templates[ $skin_name ]->Show_attachments(array (
            'attach_hits'  => $row['attach_hits'],
            'mime_image'   => $this->ipsclass->cache['attachtypes'][ $row['attach_ext'] ]['atype_img'],
            'attach_file'  => $row['attach_file'],
            'attach_id'    => $row['attach_id'],
            'type'         => $this->type,
            'file_size'    => $this->ipsclass->size_format( $row['attach_filesize'] ),
            'is_torrent'   => 0,
         )  );
    }


  Находим функцию process_upload (примерно 766 строка оригинального файла)
  В самом ее конце между кодом 
  
    $this->ipsclass->DB->do_insert( 'attachments', $attach_data );
                        
    $newid = $this->ipsclass->DB->get_insert_id();
  
  И строкой (примерно 961-я строка оригинального файла)
  
    return $newid;
  

  Вставляем свой код

   if (!strcasecmp($attach_data['attach_ext'], 'torrent'))
   {
       require_once( ROOT_PATH."sources/benc.php" );
       $torrent = bdec_file($upload->saved_upload_name, 1 << 20);
       if (!isset($torrent))
               return $attach_data;
       $bt_info_hash = pack('H*', sha1($torrent['value']['info']['string']));
       $torrent_files_count = count($torrent["value"]["info"]["value"]["files"]["value"]);
       if($torrent_files_count == 0)
       {
           $torrent_files_count = 1;
           $bt_size = $torrent["value"]["info"]["value"]["length"]["value"];
       }
       else
       {
           $bt_size = 0;
           for($torrent_i=0;$torrent_i<$torrent_files_count;$torrent_i++)
           {
               $bt_size += $torrent["value"]["info"]["value"]["files"]["value"][$torrent_i]["value"]["length"]["value"];
           }
       }
       $this->ipsclass->DB->query("insert into xbt_files (info_hash, ctime, files_size, attach_id,files_count) values ('".addslashes($bt_info_hash)."', Now(), '$bt_size', $newid, $torrent_files_count)");
   }


   Редактирование class_attach.php завершено
   Переходим к редактированию файла sources/action_public/profile.php
   Находим функцию personal_portal_view (примерно 2446-я строка)
   Находим блок (примерно 3017-я строка)

    //-----------------------------------------
    // Online location
    //-----------------------------------------
    
    $member = $this->personal_portal_get_user_location( $member );^M

    Добавляем после него свой код


    $ibz_sql = $this->ipsclass->DB->query("SELECT can_leech, wait_time, torrents_limit, downloaded, uploaded FROM xbt_users WHERE uid = '".$member['id']."' LIMIT 1");
    $this->ipsclass->DB->simple_exec();
    $r = $this->ipsclass->DB->fetch_row($ibz_sql);

    if(($r['downloaded'] <> 0)||($r['uploaded']<>0))
    {
      $member['show_torrent_stats'] = 1;
    }
    else
    {
      $member['show_torrent_stats'] = 0;
    }
    $member['show_torrent_uploaded']   = $this->ipsclass->size_format( $r['uploaded'] );
    $member['show_torrent_downloaded'] = $this->ipsclass->size_format( $r['downloaded'] );
    if($r['downloaded']==0)
    {
      $member['show_torrent_ratio'] = 'inf';
    }
    else
    {
      $member['show_torrent_ratio'] = number_format($r['uploaded'] / $r['downloaded'], 2);
    }
    if( $r['can_leech'] != 1 )
    {
      $member['show_torrent_limit'] = "запрещено скачивать";
    }
    else
    {
      if( $r['torrents_limit'] == 0 )
      {
        $member['show_torrent_limit'] = "разрешено скачивать сколько угодно торентов";
      }
      else
      {
        $member['show_torrent_limit'] = "разрешено скачивать ".$r['torrents_limit']." торент(ов|а)";
      }
    }
    if(  $r['wait_time'] != 0 )
    {
      $member['show_torrent_limit'] .= "<br>Пауза для новых торрентов = ".$r['wait_time']." секунд";
    }

  Все, редактирование завершено

  Теперь - создаем торрент-файл (в uTorrent'е я создавал с announce url="http://" и сам ставил флаг приватного торрента). 
  Прикрепляем его к своему сообщению. Затем заново его скачиваем (при этом в этот торрент-файл уже автоматически заполнится мой хеш, 
  и урл трекера) - указываем исходный файл, и вуаля. Вот картинки как это примерно выглядит:

  Вид темы

  Вид профиля

  Конечно все очень просто - но минимум необходимого для быстрого старта есть. Есть куда и развивать дальше. :)