public static function getSrcSeedURL($btih) { global $USER_AGENT; /// 1. 从数据库中查询原始页面链接 $res = get_by_btih($btih); if (!$res) { LOGW("BTIH 为 {$btih} 的资源在数据库中不存在"); return FALSE; } /// 2. 获取 link 页面内容 LOGI("正在获取动漫花园的资源页面内容: {$res['link']}"); $content = NULL; $ch = curl_init($res['link']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_ENCODING, ''); curl_setopt($ch, CURLOPT_USERAGENT, $USER_AGENT); $content = curl_exec($ch); if (!$content) { LOGE("无法抓取动漫花园的资源页面: {$res['link']}'"); return FALSE; } /// 3. 解析 BT 地址 $matches = []; $pattern = '/\\/\\/.+[a-f0-9]{40}\\.torrent/'; $ret = preg_match($pattern, $content, $matches); if ($ret >= 1) { return 'http:' . $matches[0]; } else { return FALSE; } }
/** * 下载 RSS 内容 */ protected function _fetchRss($url) { global $USER_AGENT; LOGI("正在获取 {$url}"); $content = NULL; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_ENCODING, ''); curl_setopt($ch, CURLOPT_USERAGENT, $USER_AGENT); $content = curl_exec($ch); if (!$content) { LOGE("无法抓取 RSS:`{$RSS_FEED}'"); return FALSE; } /// 2. 归档原始数据 LOGI("正在归档数据"); /// FIXME: 将 archive_raw 做成类成员函数 archive_raw($content); return $content; }
$guid = $mysqli->real_escape_string($res['guid']); $link = $mysqli->real_escape_string($res['link']); $description = $mysqli->real_escape_string($res['description']); $pubDate = $res['pubDate']; $btih = $mysqli->real_escape_string($res['btih']); $magnet = $mysqli->real_escape_string($res['magnet']); $src = $mysqli->real_escape_string($src); $ctime = time(); $mysqli->query('start transaction'); $sql = "SELECT * FROM b_resource WHERE btih='{$btih}' LIMIT 1"; $result = $mysqli->query($sql); if (!$result) { LOGE("数据库查询失败: " . $mysqli->error); continue; } if ($result->num_rows > 0) { LOGI("{$res['title']} 已存在 (因为已存在相同的 btih,btih={$btih}, src={$res['src']})"); $mysqli->query('rollback'); continue; } LOGI("保存来自 {$src} 的数据:{$res['title']}"); $sql = "INSERT INTO b_resource(title, guid, link, description, btih, pubDate, src, magnet, ctime)\n VALUES('{$title}', '{$guid}', '{$link}', '{$description}', '{$btih}', {$pubDate}, '{$src}', '{$magnet}', {$ctime})"; $ret = $mysqli->query($sql); if ($ret === FALSE) { LOGE("无法保存数据: " . $mysqli->error . ",原 SQL: " . $sql); } $mysqli->query('commit'); } } LOGI("索引完成");
if (!$result) { LOGW("数据库查询出错,跳过这个资源:" . $mysqli->error); continue; } $row = $result->fetch_assoc(); if ($row['cnt'] > 0) { LOGI("数据库中已经存在相同 btih 的资源了,跳过这个资源"); continue; } /// 3. 将这个资源添加到数据库中 LOGI("将“{$title}”保存到数据库中"); $guid = $mysqli->real_escape_string($res['guid']); $link = $mysqli->real_escape_string($res['link']); $pubDate = (int) $res['pubDate']; $description = ''; $ctime = time(); $sql = "INSERT INTO b_resource(title, guid, link, description, btih, pubDate, ctime)\n VALUES('{$title}', '{$guid}', '{$link}', '{$description}', '{$btih}', {$pubDate}, {$ctime})"; $ret = $mysqli->query($sql); if ($ret === FALSE) { LOGW("数据库查询出错:" . $mysqli->error); } else { $cnt_new++; } } } if ($DRY_RUN) { LOGI("目前运行在测试模式,将回滚数据库"); $mysqli->rollback(); } LOGI("资源索引完成,分析得到 {$cnt_count} 个资源,共新添加了 {$cnt_new} 个资源");
} /// 2.2 开始下载 $content = NULL; $ch = curl_init($url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_ENCODING, ''); curl_setopt($ch, CURLOPT_USERAGENT, $USER_AGENT); curl_setopt($ch, CURLOPT_REFERER, ''); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_MAXREDIRS, 5); $content = curl_exec($ch); if (!$content || curl_error($ch)) { LOGD("无法从漫游下载种子({$url}):" . curl_error($ch)); header('HTTP/1.1 500 Internal Error'); die('<h1>500 Internal Error</h1>'); } LOGI("保存种子“{$btih}”,并将用户跳转到下载地址"); $path = archive_torrent($content, $btih); /// 3. 检查下载的内容是否是种子,漫游在发生错误的时候仍会返回 HTTP 200,所以我们需要通过 MIME 来进行检查 if (!$path || mime_content_type($path) != 'application/x-bittorrent') { if ($path) { unlink($path); } LOGD("从漫游下载到的种子不是合法的 ({$url}):" . curl_error($ch)); header('HTTP/1.1 500 Internal Error'); die('<h1>500 Internal Error</h1>'); } header("Content-Disposition: attachment; filename={$btih}.torrent"); header('Content-Type: application/x-bittorrent'); ob_clean(); echo $content;
/** * 处理announce_peer请求 * @param array $msg 接收到的announce_peer请求数据 * @param array $address 对端链接信息 * @return void */ function on_announce_peer($msg, $address) { global $nid; // 获取infohash $infohash = $msg['a']['info_hash']; // 获取token $token = $msg['a']['token']; // 获取node id $id = $msg['a']['id']; // 验证token是否正确 if (substr($infohash, 0, 2) == $token) { /*$txt = array( 'action' => 'announce_peer', 'msg' => array( 'ip' => $address[0], 'port1' => $address[1], 'port2' => $msg['a']['port'], 'infohash' => $infohash ) ); var_dump($txt);*/ $nodeid = bin2hex($id); LOGI("(node_id={$nodeid}) 获取到info_hash: " . strtoupper(bin2hex($infohash))); logDHTAnnouncePeer($nodeid, bin2hex($infohash)); } // 生成回复数据 $msg = array('t' => $msg['t'], 'y' => 'r', 'r' => array('id' => $nid)); // 发送请求回复 send_response($msg, $address); }
LOGI("已保存磁力链接: {$magnet}"); /// 3. 下载 BT 种子文件 $r = rand() % 20 + 10; LOGI("等待 {$r} 秒后去下载种子文件"); sleep($r); $content = NULL; $ch = curl_init($res['guid']); curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); curl_setopt($ch, CURLOPT_ENCODING, ''); curl_setopt($ch, CURLOPT_USERAGENT, $USER_AGENT); curl_setopt($ch, CURLOPT_REFERER, $res['link']); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); curl_setopt($ch, CURLOPT_MAXREDIRS, 5); $content = curl_exec($ch); if (!$content) { LOGE("无法下载种子文件:`{$res['guid']}'"); die(''); } $btih = $res['btih']; if ($btih == '') { $match = array(); preg_match('([0-9a-f]{40})', $res['link'], $match); $btih = $match[0]; } if ($btih == '') { LOGE('无法获得种子文件的 BTIH,无法保存种子文件'); die(''); } archive_torrent($content, $btih); LOGI("“{$res['title']}”处理完毕");
/** * 保存一个 announce_peer 数据,同时更新资源的 7 日下载数 */ function logDHTAnnouncePeer($node_id, $btih) { /// 1. 检验数据合法性 $pattern = '/^[0-9a-f]{40}$/i'; if (!preg_match($pattern, $node_id)) { LOGW("传入的 node_id({$node_id}) 格式不合法"); return FALSE; } if (!preg_match($pattern, $btih)) { LOGW("传入的 btih({$btih}) 格式不合法"); return FALSE; } /// 2. 记录此次 announce_peer $ctime = time(); $sql = "INSERT INTO b_dht_log (node_id, btih, ctime) VALUES (UNHEX('{$node_id}'), UNHEX('{$btih}'), {$ctime})"; $result = db_query($sql); if (!$result) { LOGE("数据库查询失败"); return FALSE; } /// 3. 更新资源热度(如果对应的 btih 存在) $sql = "SELECT * FROM b_resource WHERE btih='{$btih}'"; $result = db_query($sql); if ($result->num_rows <= 0) { /// 资源不存在,无需更新 return TRUE; } else { $res = $result->fetch_assoc(); if ($res['popularity'] < 0) { LOGI("初始化资源 {$btih} 的热度"); init_popularity($btih); } return update_popularity($res, $ctime); } }
<?php /** * popgo-gone 版本之后,b_resource 表中的 btih 字段变为 UNIQUE 索引,该脚本用于更新 btih 为空的行,以便修改 btih 索引为 UNIQUE 索引 */ require_once '../header.php'; $sql = "SELECT * FROM b_resource WHERE src='popgo' AND btih=''"; $res = $mysqli->query($sql) or die($mysqli->error); if (!$res) { LOGI("已经没有需要更新的资源了"); exit(0); } LOGI(sprintf("需要更新 %d 个资源", $res->num_rows)); while ($row = $res->fetch_assoc()) { $btih = popgo_get_btih_from_link($row['guid']); $sql = "UPDATE b_resource SET btih='{$btih}' WHERE resource_id={$row['resource_id']}"; $mysqli->query($sql) or die($mysqli->error); }