commit
822e4a2270
@ -0,0 +1,5 @@ |
||||
/app/torrent |
||||
/app/config/config.php |
||||
/php-7.2.12-nts |
||||
/*.bat |
||||
/*.sh |
@ -0,0 +1,58 @@ |
||||
木兰宽松许可证, 第1版 |
||||
2019年8月 http://license.coscl.org.cn/MulanPSL |
||||
|
||||
您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第1版(“本许可证”)的如下条款的约束: |
||||
|
||||
0. 定义 |
||||
|
||||
“软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 |
||||
|
||||
“贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 |
||||
|
||||
“法人实体”是指提交贡献的机构及其“关联实体”。 |
||||
|
||||
“关联实体”是指,对“本许可证”下的一方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 |
||||
|
||||
“贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 |
||||
|
||||
1. 授予版权许可 |
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 |
||||
|
||||
2. 授予专利许可 |
||||
|
||||
每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括仅因您或他人修改“贡献”或其他结合而将必然会侵犯到的专利权利要求。如您或您的“关联实体”直接或间接地(包括通过代理、专利被许可人或受让人),就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 |
||||
|
||||
3. 无商标许可 |
||||
|
||||
“本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 |
||||
|
||||
4. 分发限制 |
||||
|
||||
您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 |
||||
|
||||
5. 免责声明与责任限制 |
||||
|
||||
“软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 |
||||
|
||||
条款结束 |
||||
|
||||
如何将木兰宽松许可证,第1版,应用到您的软件 |
||||
|
||||
如果您希望将木兰宽松许可证,第1版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: |
||||
|
||||
1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; |
||||
|
||||
2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; |
||||
|
||||
3, 请将如下声明文本放入每个源文件的头部注释中。 |
||||
|
||||
Copyright (c) [2019] [name of copyright holder] |
||||
[Software Name] is licensed under the Mulan PSL v1. |
||||
You can use this software according to the terms and conditions of the Mulan PSL v1. |
||||
You may obtain a copy of Mulan PSL v1 at: |
||||
http://license.coscl.org.cn/MulanPSL |
||||
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR |
||||
PURPOSE. |
||||
See the Mulan PSL v1 for more details. |
@ -0,0 +1,141 @@ |
||||
<?php |
||||
/** |
||||
* Created by PhpStorm. |
||||
* User: Rhilip |
||||
* Date: 2019/4/21 |
||||
* Time: 21:12 |
||||
*/ |
||||
class Bencode |
||||
{ |
||||
/** |
||||
* Decodes a BEncoded string to the following values: |
||||
* - Dictionary (starts with d, ends with e) |
||||
* - List (starts with l, ends with e |
||||
* - Integer (starts with i, ends with e |
||||
* - String (starts with number denoting number of characters followed by : and then the string) |
||||
* |
||||
* @see https://wiki.theory.org/index.php/BitTorrentSpecification |
||||
* |
||||
* @param string $data |
||||
* @param int $pos |
||||
* @return mixed |
||||
*/ |
||||
public static function decode($data, &$pos = 0) |
||||
{ |
||||
$start_decode = ($pos === 0); |
||||
if ($data[$pos] === 'd') { |
||||
$pos++; |
||||
$return = []; |
||||
while ($data[$pos] !== 'e') { |
||||
$key = self::decode($data, $pos); |
||||
$value = self::decode($data, $pos); |
||||
if ($key === null || $value === null) { |
||||
break; |
||||
} |
||||
if (!is_string($key)) { |
||||
throw new RuntimeException('Invalid key type, must be string: ' . gettype($key)); |
||||
} |
||||
$return[$key] = $value; |
||||
} |
||||
ksort($return); |
||||
$pos++; |
||||
} elseif ($data[$pos] === 'l') { |
||||
$pos++; |
||||
$return = []; |
||||
while ($data[$pos] !== 'e') { |
||||
$value = self::decode($data, $pos); |
||||
$return[] = $value; |
||||
} |
||||
$pos++; |
||||
} elseif ($data[$pos] === 'i') { |
||||
$pos++; |
||||
$digits = strpos($data, 'e', $pos) - $pos; |
||||
$return = substr($data, $pos, $digits); |
||||
if ($return === '-0') { |
||||
throw new RuntimeException('Cannot have integer value -0'); |
||||
} |
||||
$multiplier = 1; |
||||
if ($return[0] === '-') { |
||||
$multiplier = -1; |
||||
$return = substr($return, 1); |
||||
} |
||||
if (!ctype_digit($return)) { |
||||
throw new RuntimeException('Cannot have non-digit values in integer number: ' . $return); |
||||
} |
||||
$return = $multiplier * ((int)$return); |
||||
$pos += $digits + 1; |
||||
} else { |
||||
$digits = strpos($data, ':', $pos) - $pos; |
||||
$len = (int)substr($data, $pos, $digits); |
||||
$pos += ($digits + 1); |
||||
$return = substr($data, $pos, $len); |
||||
$pos += $len; |
||||
} |
||||
if ($start_decode) { |
||||
if ($pos !== strlen($data)) { |
||||
throw new RuntimeException('Could not fully decode bencode string'); |
||||
} |
||||
} |
||||
return $return; |
||||
} |
||||
/** |
||||
* @param mixed $data |
||||
* @return string |
||||
*/ |
||||
public static function encode($data) |
||||
{ |
||||
if (is_array($data)) { |
||||
$return = ''; |
||||
$check = -1; |
||||
$list = true; |
||||
foreach ($data as $key => $value) { |
||||
if ($key !== ++$check) { |
||||
$list = false; |
||||
break; |
||||
} |
||||
} |
||||
if ($list) { |
||||
$return .= 'l'; |
||||
foreach ($data as $value) { |
||||
$return .= self::encode($value); |
||||
} |
||||
} else { |
||||
$return .= 'd'; |
||||
foreach ($data as $key => $value) { |
||||
$return .= self::encode(strval($key)); |
||||
$return .= self::encode($value); |
||||
} |
||||
} |
||||
$return .= 'e'; |
||||
} elseif (is_integer($data)) { |
||||
$return = 'i' . $data . 'e'; |
||||
} else { |
||||
$return = strlen($data) . ':' . $data; |
||||
} |
||||
return $return; |
||||
} |
||||
/** |
||||
* Given a path to a file, decode the contents of it |
||||
* |
||||
* @param string $path |
||||
* @return mixed |
||||
*/ |
||||
public static function load($path) |
||||
{ |
||||
if (is_file($path)) { |
||||
return self::decode(file_get_contents($path)); |
||||
} |
||||
return null; |
||||
} |
||||
/** |
||||
* Given a path for a file, encode the contents of it |
||||
* |
||||
* @param string $path |
||||
* @param $data |
||||
* @return mixed |
||||
*/ |
||||
public static function dump($path, $data) |
||||
{ |
||||
return file_put_contents($path, self::encode($data)); |
||||
} |
||||
} |
@ -0,0 +1,313 @@ |
||||
<?php |
||||
/** |
||||
* 调试函数 |
||||
*/ |
||||
function p($data, $echo=true){ |
||||
$str='******************************'."\n"; |
||||
// 如果是boolean或者null直接显示文字;否则print |
||||
if (is_bool($data)) { |
||||
$show_data=$data ? 'true' : 'false'; |
||||
}elseif (is_null($data)) { |
||||
$show_data='null'; |
||||
}else{ |
||||
$show_data=print_r($data,true); |
||||
} |
||||
$str.=$show_data; |
||||
$str.="\n".'******************************'."\n"; |
||||
if($echo){ |
||||
echo $str; |
||||
return null; |
||||
} |
||||
return $str; |
||||
} |
||||
|
||||
/** |
||||
* 微信推送Server酱 |
||||
*/ |
||||
function sc($text='', $desp='') |
||||
{ |
||||
global $configALL; |
||||
$token = $configALL['sc.ftqq.com']; |
||||
$desp = ($desp=='')?date("Y-m-d H:i:s") :$desp; |
||||
$postdata = http_build_query(array( |
||||
'text' => $text, |
||||
'desp' => $desp |
||||
)); |
||||
$opts = array('http' => array( |
||||
'method' => 'POST', |
||||
'header' => 'Content-type: application/x-www-form-urlencoded', |
||||
'content' => $postdata |
||||
)); |
||||
$context = stream_context_create($opts); |
||||
$result = file_get_contents('http://sc.ftqq.com/'.$token.'.send', false, $context); |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* 微信推送 爱语飞飞 |
||||
*/ |
||||
function ff($text='', $desp='') |
||||
{ |
||||
global $configALL; |
||||
$token = $configALL['iyuu.cn']; |
||||
$desp = ($desp=='')?date("Y-m-d H:i:s") :$desp; |
||||
$postdata = http_build_query(array( |
||||
'text' => $text, |
||||
'desp' => $desp |
||||
)); |
||||
$opts = array('http' => array( |
||||
'method' => 'POST', |
||||
'header' => 'Content-type: application/x-www-form-urlencoded', |
||||
'content' => $postdata |
||||
)); |
||||
$context = stream_context_create($opts); |
||||
$result = file_get_contents('http://iyuu.cn/'.$token.'.send', false, $context); |
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* 微信推送 爱语飞飞 |
||||
* @param array $torrent 种子数组 |
||||
Array |
||||
( |
||||
[id] => 118632 |
||||
[h1] => CCTV5+ 2019 ATP Men's Tennis Final 20191115B HDTV 1080i H264-HDSTV |
||||
[title] => 央视体育赛事频道 2019年ATP男子网球年终总决赛 单打小组赛 纳达尔VS西西帕斯 20191115[优惠剩余时间:4时13分] |
||||
[details] => https://xxx.me/details.php?id=118632 |
||||
[download] => https://xxx.me/download.php?id=118632 |
||||
[filename] => 118632.torrent |
||||
[type] => 0 |
||||
[sticky] => 1 |
||||
[time] => Array |
||||
( |
||||
[0] => "2019-11-16 20:41:53">4时13分 |
||||
[1] => "2019-11-16 14:41:53">1时<br />46分 |
||||
) |
||||
[comments] => 0 |
||||
[size] => 5232.64MB |
||||
[seeders] => 69 |
||||
[leechers] => 10 |
||||
[completed] => 93 |
||||
[percentage] => 100% |
||||
[owner] => 匿名 |
||||
) |
||||
*/ |
||||
function send($site = '', $torrent = array()) |
||||
{ |
||||
$br = "\r\n"; |
||||
$text = $site. ' 免费:' .$torrent['filename']. ',添加成功'; |
||||
$desp = '主标题:'.$torrent['h1'] . $br; |
||||
|
||||
if ( isset($torrent['title']) ) { |
||||
$desp .= '副标题:'.$torrent['title']. $br; |
||||
} |
||||
if ( isset($torrent['size']) ) { |
||||
$desp .= '大小:'.$torrent['size']. $br; |
||||
} |
||||
if ( isset($torrent['seeders']) ) { |
||||
$desp .= '做种数:'.$torrent['seeders']. $br; |
||||
} |
||||
if ( isset($torrent['leechers']) ) { |
||||
$desp .= '下载数:'.$torrent['leechers']. $br; |
||||
} |
||||
if ( isset($torrent['owner']) ) { |
||||
$desp .= '发布者:'.$torrent['owner']. $br; |
||||
} |
||||
return ff($text, $desp); |
||||
} |
||||
|
||||
/** |
||||
* @brief 下载种子 |
||||
* @param string $url 种子URL |
||||
* @param string $cookies 模拟登陆的cookie |
||||
* @return mixed 返回的数据 |
||||
*/ |
||||
function download($url, $cookies, $useragent, $method = 'GET') |
||||
{ |
||||
$header = array( |
||||
"Content-Type:application/x-www-form-urlencoded", |
||||
'User-Agent: '.$useragent); |
||||
$ch = curl_init(); |
||||
if($method === 'POST'){ |
||||
curl_setopt($ch, CURLOPT_POST, true ); |
||||
} |
||||
if(stripos($url, 'https://') !== FALSE) { |
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); |
||||
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); |
||||
curl_setopt($ch, CURLOPT_SSLVERSION, 1); |
||||
} |
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
||||
curl_setopt($ch, CURLOPT_HTTPHEADER,$header); |
||||
curl_setopt($ch, CURLOPT_COOKIE,$cookies); |
||||
curl_setopt($ch, CURLOPT_URL, $url); |
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT,60); |
||||
curl_setopt($ch, CURLOPT_TIMEOUT,600); |
||||
$data = curl_exec($ch); |
||||
$status = curl_getinfo($ch); |
||||
curl_close($ch); |
||||
if (isset($status['http_code']) && $status['http_code'] == 200) { |
||||
return $data; |
||||
} |
||||
if (isset($status['http_code']) && $status['http_code'] == 302) { |
||||
return download($status['redirect_url'], $cookies, $useragent); |
||||
} |
||||
return $data; |
||||
} |
||||
|
||||
/** |
||||
* @brief 文件大小格式化为MB |
||||
* @param string $from 文件大小 |
||||
* @return int 单位MB |
||||
*/ |
||||
function convertToMB($from){ |
||||
$number=substr($from,0,-2); |
||||
switch(strtoupper(substr($from,-2))){ |
||||
case "KB": |
||||
return $number/1024; |
||||
case "MB": |
||||
return $number; |
||||
case "GB": |
||||
return $number*pow(1024,1); |
||||
case "TB": |
||||
return $number*pow(1024,2); |
||||
case "PB": |
||||
return $number*pow(1024,3); |
||||
default: |
||||
return $from; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @brief 种子过滤器 |
||||
* @param string $site 站点标识 |
||||
* @param array $torrent 种子数组 |
||||
* Array |
||||
( |
||||
[id] => 118632 |
||||
[h1] => CCTV5+ 2019 ATP Men's Tennis Final 20191115B HDTV 1080i H264-HDSTV |
||||
[title] => 央视体育赛事频道 2019年ATP男子网球年终总决赛 单打小组赛 纳达尔VS西西帕斯 20191115[优惠剩余时间:4时13分] |
||||
[details] => https://xxx.me/details.php?id=118632 |
||||
[download] => https://xxx.me/download.php?id=118632 |
||||
[filename] => 118632.torrent |
||||
[type] => 0 |
||||
[sticky] => 1 |
||||
[time] => Array |
||||
( |
||||
[0] => "2019-11-16 20:41:53">4时13分 |
||||
[1] => "2019-11-16 14:41:53">1时<br />46分 |
||||
) |
||||
[comments] => 0 |
||||
[size] => 5232.64MB |
||||
[seeders] => 69 |
||||
[leechers] => 10 |
||||
[completed] => 93 |
||||
[percentage] => 100% |
||||
[owner] => 匿名 |
||||
) |
||||
* @return bool 或 string false不过滤 |
||||
*/ |
||||
function filter($site = '', $torrent = array()){ |
||||
global $configALL; |
||||
$config = $configALL[$site]; |
||||
$filter = array(); |
||||
// 读取配置 |
||||
if (isset($configALL['default']['filter']) || isset($config['filter'])) { |
||||
$filter = isset($config['filter']) && $config['filter'] ? $config['filter'] : $configALL['default']['filter']; |
||||
}else { |
||||
return false; |
||||
} |
||||
$filename = $torrent['filename']; |
||||
|
||||
// 兼容性 |
||||
if ( empty($torrent['size']) ) { |
||||
return false; |
||||
} |
||||
// 大小过滤 |
||||
$size = convertToMB($torrent['size']); |
||||
$min = isset($filter['size']['min']) ? convertToMB($filter['size']['min']) : 0; |
||||
$max = isset($filter['size']['max']) ? convertToMB($filter['size']['max']) : 2097152; //默认 2097152MB = 2TB |
||||
if ($min > $size || $size > $max) { |
||||
return $filename. ' ' .$size. 'MB,被大小过滤'; |
||||
} |
||||
|
||||
// 兼容性 |
||||
if ( empty($torrent['seeders']) ) { |
||||
return false; |
||||
} |
||||
// 种子数过滤 |
||||
$seeders = $torrent['seeders']; |
||||
$min = isset($filter['seeders']['min']) ? $filter['seeders']['min'] : 1; //默认 1 |
||||
$max = isset($filter['seeders']['max']) ? $filter['seeders']['max'] : 3; //默认 3 |
||||
if ($min > $seeders || $seeders > $max) { |
||||
return $filename. ' 当前做种' .$seeders. '人,被过滤'; |
||||
} |
||||
|
||||
// 兼容性 |
||||
if ( empty($torrent['leechers']) ) { |
||||
return false; |
||||
} |
||||
// 下载数过滤 |
||||
$leechers = $torrent['leechers']; |
||||
$min = isset($filter['leechers']['min']) ? $filter['leechers']['min'] : 0; //默认 |
||||
$max = isset($filter['leechers']['max']) ? $filter['leechers']['max'] : 30000; //默认 |
||||
if ($min > $leechers || $leechers > $max) { |
||||
return $filename. ' 当前下载' .$leechers. '人,被过滤'; |
||||
} |
||||
|
||||
// 兼容性 |
||||
if ( empty($torrent['completed']) ) { |
||||
return false; |
||||
} |
||||
// 完成数过滤 |
||||
$completed = $torrent['completed']; |
||||
$min = isset($filter['completed']['min']) ? $filter['completed']['min'] : 0; //默认 |
||||
$max = isset($filter['completed']['max']) ? $filter['completed']['max'] : 30000; //默认 |
||||
if ($min > $completed || $completed > $max) { |
||||
return $filename. ' 已完成数' .$completed. '人,被过滤'; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 奇数 |
||||
*/ |
||||
function oddFilter($var) |
||||
{ |
||||
// 返回$var最后一个二进制位, |
||||
// 为1则保留(奇数的二进制的最后一位肯定是1) |
||||
return($var & 1); |
||||
} |
||||
|
||||
/** |
||||
* 偶数 |
||||
*/ |
||||
function evenFilter($var) |
||||
{ |
||||
// 返回$var最后一个二进制位, |
||||
// 为0则保留(偶数的二进制的最后一位肯定是0) |
||||
return(!($var & 1)); |
||||
} |
||||
|
||||
/** |
||||
* 发布员签名 |
||||
* 注意:同时配置iyuu.cn与secret时,优先使用secret。 |
||||
*/ |
||||
function sign( $timestamp ){ |
||||
global $configALL; |
||||
// 爱语飞飞 |
||||
$token = isset($configALL['iyuu.cn']) && $configALL['iyuu.cn'] ? $configALL['iyuu.cn'] : ''; |
||||
// 鉴权 |
||||
$token = isset($configALL['secret']) && $configALL['secret'] ? $configALL['secret'] : $token; |
||||
return sha1($timestamp . $token); |
||||
} |
||||
|
||||
/** |
||||
* @brief 分离token中的用户uid |
||||
* token算法:IYUU + uid + T + sha1(openid+time+盐) |
||||
* @param string $token 用户请求token |
||||
*/ |
||||
function getUid($token){ |
||||
//验证是否IYUU开头,strpos($token,'T')<15,token总长度小于60(40+10+5) |
||||
return (strlen($token)<60)&&(strpos($token,'IYUU')===0)&&(strpos($token,'T')<15) ? substr($token,4,strpos($token,'T')-4): false; |
||||
} |
@ -0,0 +1,332 @@ |
||||
<?php |
||||
/** |
||||
* @brief 文件处理 |
||||
* @version 0.6 |
||||
*/ |
||||
|
||||
/** |
||||
* @class IFile |
||||
* @brief IFile 文件处理类 |
||||
*/ |
||||
class IFile |
||||
{ |
||||
private $resource = null; //文件资源句柄 |
||||
|
||||
/** |
||||
* @brief 构造函数,打开资源流,并独占锁定 |
||||
* @param String $fileName 文件路径名 |
||||
* @param String $mode 操作方式,默认为读操作,可供选择的项为:r,r+,w+,w+,a,a+ |
||||
* @note $mod,'r' 只读方式打开,将文件指针指向文件头 |
||||
* 'r+' 读写方式打开,将文件指针指向文件头 |
||||
* 'w' 写入方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。 |
||||
* 'w+' 读写方式打开,将文件指针指向文件头并将文件大小截为零。如果文件不存在则尝试创建之。 |
||||
* 'a' 写入方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。 |
||||
* 'a+' 读写方式打开,将文件指针指向文件末尾。如果文件不存在则尝试创建之。 |
||||
*/ |
||||
function __construct($fileName,$mode='r') |
||||
{ |
||||
$dirName = dirname($fileName); |
||||
$baseName = basename($fileName); |
||||
|
||||
//检查并创建文件夹 |
||||
self::mkdir($dirName); |
||||
|
||||
$this->resource = fopen($fileName,$mode.'b'); |
||||
if($this->resource) |
||||
{ |
||||
flock($this->resource,LOCK_EX); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @brief 获取文件内容 |
||||
* @return String 文件内容 |
||||
*/ |
||||
public function read() |
||||
{ |
||||
$content = null; |
||||
while(!feof($this->resource)) |
||||
{ |
||||
$content.= fread($this->resource,1024); |
||||
} |
||||
return $content; |
||||
} |
||||
|
||||
/** |
||||
* @brief 文件写入操作 |
||||
* @param String $content 要写入的文件内容 |
||||
* @return Int or false 写入的字符数; false:写入失败; |
||||
*/ |
||||
public function write($content) |
||||
{ |
||||
$worldsnum = fwrite($this->resource,$content); |
||||
$this->save(); |
||||
return is_bool($worldsnum) ? false : $worldsnum; |
||||
} |
||||
|
||||
/** |
||||
* @brief 清空目录下的所有文件 |
||||
* @return bool false:失败; true:成功; |
||||
*/ |
||||
public static function clearDir($dir) |
||||
{ |
||||
if($dir[0] != '.' && is_dir($dir) && is_writable($dir)) |
||||
{ |
||||
$dirRes = opendir($dir); |
||||
while( false !== ($fileName = readdir($dirRes)) ) |
||||
{ |
||||
if($fileName[0] !== '.') |
||||
{ |
||||
$fullpath = $dir.'/'.$fileName; |
||||
if(is_file($fullpath)) |
||||
{ |
||||
self::unlink($fullpath); |
||||
} |
||||
else |
||||
{ |
||||
self::clearDir($fullpath); |
||||
rmdir($fullpath); |
||||
} |
||||
} |
||||
} |
||||
closedir($dirRes); |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @brief 获取文件信息 |
||||
* @param String $fileName 文件路径 |
||||
* @return array or null array:文件信息; null:文件不存在; |
||||
*/ |
||||
public static function getInfo($fileName) |
||||
{ |
||||
if(is_file($fileName)) |
||||
return stat($fileName); |
||||
|
||||
else |
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* @brief 创建文件夹 |
||||
* @param String $path 路径 |
||||
* @param int $chmod 文件夹权限 |
||||
* @note $chmod 参数不能是字符串(加引号),否则linux会出现权限问题 |
||||
*/ |
||||
public static function mkdir($path,$chmod=0777) |
||||
{ |
||||
return is_dir($path) or (self::mkdir(dirname($path),$chmod) and mkdir($path,$chmod)); |
||||
} |
||||
|
||||
/** |
||||
* @brief 复制文件 |
||||
* @param String $from 源文件路径 |
||||
* @param String $to 目标文件路径 |
||||
* @param String $mod 操作模式,c:复制(默认); x:剪切(删除$from文件) |
||||
* @return bool 操作结果 true:成功; false:失败; |
||||
*/ |
||||
public static function copy($from,$to,$mode = 'c') |
||||
{ |
||||
$dir = dirname($to); |
||||
|
||||
//创建目录 |
||||
self::mkdir($dir); |
||||
|
||||
copy($from,$to); |
||||
|
||||
if(is_file($to)) |
||||
{ |
||||
if($mode == 'x') |
||||
{ |
||||
self::unlink($from); |
||||
} |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @brief 删除文件 |
||||
* @param String $fileName 文件路径 |
||||
* @return bool 操作结果 false:删除失败; |
||||
*/ |
||||
public static function unlink($fileName) |
||||
{ |
||||
if(is_file($fileName) && is_writable($fileName)) |
||||
{ |
||||
return unlink($fileName); |
||||
} |
||||
else |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* @brief 删除$dir文件夹 或者 其下所有文件 |
||||
* @param String $dir 文件路径 |
||||
* @param bool $recursive 是否强制删除,如果强制删除则递归删除该目录下的全部文件,默认为false |
||||
* @return bool true:删除成功; false:删除失败; |
||||
*/ |
||||
public static function rmdir($dir,$recursive = false) |
||||
{ |
||||
if(is_dir($dir) && is_writable($dir)) |
||||
{ |
||||
//强制删除 |
||||
if($recursive == true) |
||||
{ |
||||
self::clearDir($dir); |
||||
return self::rmdir($dir,false); |
||||
} |
||||
|
||||
//非强制删除 |
||||
else |
||||
{ |
||||
if(rmdir($dir)) |
||||
{ |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @brief 获取文件类型 |
||||
* @param String $fileName 文件名 |
||||
* @return String $filetype 文件类型 |
||||
* @note 如果文件不存在,返回false,如果文件后缀名不在识别列表之内,返回NULL,对于docx及elsx格式文档识别在会出现识别为ZIP格式的错误,这是office2007的bug目前尚未修复,请谨慎使用 |
||||
*/ |
||||
public static function getFileType($fileName) |
||||
{ |
||||
$filetype = null; |
||||
if(!is_file($fileName)) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
$fileRes = fopen($fileName,"rb"); |
||||
if(!$fileRes) |
||||
{ |
||||
return false; |
||||
} |
||||
$bin= fread($fileRes, 2); |
||||
fclose($fileRes); |
||||
|
||||
if($bin != null) |
||||
{ |
||||
$strInfo = unpack("C2chars", $bin); |
||||
$typeCode = intval($strInfo['chars1'].$strInfo['chars2']); |
||||
$typelist = self::getTypeList(); |
||||
foreach($typelist as $val) |
||||
{ |
||||
if(strtolower($val[0]) == strtolower($typeCode)) |
||||
{ |
||||
if($val[0] == 8075) |
||||
{ |
||||
return array('zip','docx','xlsx'); |
||||
} |
||||
else |
||||
{ |
||||
return $val[1]; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
return $filetype; |
||||
} |
||||
|
||||
/** |
||||
* @brief 获取文件类型映射关系 |
||||
* @return array 文件类型映射关系数组 |
||||
*/ |
||||
public static function getTypeList() |
||||
{ |
||||
return array( |
||||
array('255216','jpg'), |
||||
array('13780','png'), |
||||
array('7173','gif'), |
||||
array('6677','bmp'), |
||||
array('6063','xml'), |
||||
array('60104','html'), |
||||
array('208207','xls/doc'), |
||||
array('8075','zip'), |
||||
array('8075','docx'), |
||||
array('8075','xlsx'), |
||||
array("8297","rar"), |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* @brief 获取文件大小 |
||||
* @param String $fileName 文件名 |
||||
* @return Int 文件大小的字节数,如果文件无效则返回 NULL |
||||
*/ |
||||
public static function getFileSize($fileName) |
||||
{ |
||||
return is_file($fileName) ? filesize($fileName):null; |
||||
} |
||||
|
||||
/** |
||||
* @brief 检测文件夹是否为空 |
||||
* @param String $dir 路径地址 |
||||
* @return bool true:$dir为空目录; false:$dir为非空目录; |
||||
*/ |
||||
public static function isEmptyDir($dir) |
||||
{ |
||||
if(is_dir($dir)) |
||||
{ |
||||
$isEmpty = true; |
||||
$dirRes = opendir($dir); |
||||
while(false !== ($fileName = readdir($dirRes))) |
||||
{ |
||||
if($fileName!='.' && $fileName!='..') |
||||
{ |
||||
$isEmpty = false; |
||||
break; |
||||
} |
||||
} |
||||
closedir($dirRes); |
||||
return $isEmpty; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @brief 释放文件锁定 |
||||
*/ |
||||
public function save() |
||||
{ |
||||
flock($this->resource,LOCK_UN); |
||||
} |
||||
|
||||
/** |
||||
* @brief 获取文件扩展名 |
||||
* @param String $fileName 文件名 |
||||
* @return String 文件后缀名 |
||||
*/ |
||||
public static function getFileSuffix($fileName) |
||||
{ |
||||
$fileInfoArray = pathinfo($fileName); |
||||
return $fileInfoArray['extension']; |
||||
} |
||||
|
||||
/** |
||||
* @brief 析构函数,释放文件连接句柄 |
||||
*/ |
||||
function __destruct() |
||||
{ |
||||
if(is_resource($this->resource)) |
||||
{ |
||||
fclose($this->resource); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,71 @@ |
||||
<?php |
||||
/** |
||||
* IYUU用户注册、认证 |
||||
*/ |
||||
use Curl\Curl; |
||||
class Oauth{ |
||||
// 合作的站点 |
||||
public static $sites = ['ourbits']; |
||||
// 爱语飞飞token |
||||
public static $token = ''; |
||||
// 合作站点用户id |
||||
public static $user_id = 0; |
||||
// 合作站点密钥 |
||||
public static $passkey = ''; |
||||
// 合作站名字 |
||||
public static $site = ''; |
||||
/** |
||||
* 初始化配置 |
||||
*/ |
||||
public static function init(){ |
||||
global $configALL; |
||||
foreach (self::$sites as $name) { |
||||
if (isset($configALL[$name]['passkey']) && $configALL[$name]['passkey'] && isset($configALL[$name]['id']) && $configALL[$name]['id'] ) { |
||||
self::$token = self::getSign(); |
||||
self::$user_id = $configALL[$name]['id']; |
||||
self::$passkey = sha1( $configALL[$name]['passkey'] ); // 避免泄露用户passkey秘钥 |
||||
self::$site = $name; |
||||
return true; |
||||
} |
||||
} |
||||
echo "-----缺少合作站点登录参数:token, user_id, passkey, site \n"; |
||||
echo "-----当前正在使用测试接口,功能可能会受到限制! \n\n"; |
||||
return false; |
||||
} |
||||
/** |
||||
* 从配置文件内读取爱语飞飞token作为鉴权参数 |
||||
*/ |
||||
public static function getSign(){ |
||||
global $configALL; |
||||
// 爱语飞飞 |
||||
$token = isset($configALL['iyuu.cn']) && $configALL['iyuu.cn'] ? $configALL['iyuu.cn'] : ''; |
||||
if (empty($token) || strlen($token)<46) { |
||||
echo "缺少辅种接口请求参数:爱语飞飞token \n"; |
||||
echo "请访问https://iyuu.cn 用微信扫码申请,并填入配置文件config.php内。\n\n"; |
||||
exit(1); |
||||
} |
||||
return $token; |
||||
} |
||||
/** |
||||
* 用户注册与登录 |
||||
* 作用:在服务器端实现微信用户与合作站点用户id的关联 |
||||
* 参数:爱语飞飞token + 合作站点用户id + sha1(合作站点密钥passkey) + 合作站点标识 |
||||
*/ |
||||
public static function login($apiUrl = ''){ |
||||
$is_oauth = self::init(); |
||||
if ( $is_oauth ) { |
||||
$curl = new Curl(); |
||||
$curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); |
||||
$data = [ |
||||
'token' => self::$token, |
||||
'id' => self::$user_id, |
||||
'passkey'=> self::$passkey, |
||||
'site' => self::$site, |
||||
]; |
||||
$res = $curl->get($apiUrl, $data); |
||||
p($res->response); |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
} |
@ -0,0 +1,368 @@ |
||||
<?php |
||||
/** |
||||
* Rpc操作类 |
||||
*/ |
||||
class Rpc |
||||
{ |
||||
/** |
||||
* 版本号 |
||||
* @var string |
||||
*/ |
||||
const VER = '0.0.1'; |
||||
|
||||
// 下载种子的请求类型 GET POST |
||||
public static $method = 'GET'; |
||||
// RPC连接池 |
||||
public static $links = array(); |
||||
/** |
||||
* cookie |
||||
*/ |
||||
public static $cookies = ''; |
||||
/** |
||||
* 浏览器 User-Agent |
||||
*/ |
||||
public static $userAgent = ''; |
||||
/** |
||||
* passkey |
||||
*/ |
||||
public static $passkey = ''; |
||||
/** |
||||
* 客户端配置 |
||||
*/ |
||||
public static $clients = ''; |
||||
/** |
||||
* 监控目录 |
||||
*/ |
||||
public static $watch = ''; |
||||
/** |
||||
* 种子存放路径 |
||||
*/ |
||||
public static $torrentDir = ''; |
||||
/** |
||||
* 工作模式 |
||||
*/ |
||||
public static $workingMode = ''; |
||||
// 站点标识 |
||||
public static $site = ''; |
||||
/** |
||||
* 负载均衡 控制变量 |
||||
*/ |
||||
public static $RPC_Key = 0; |
||||
/** |
||||
* 退出状态码 |
||||
*/ |
||||
public static $ExitCode = 0; |
||||
/** |
||||
* 初始化 |
||||
*/ |
||||
public static function init($site = '', $method = 'GET'){ |
||||
global $configALL; |
||||
|
||||
self::$site = $site; |
||||
self::$method = strtoupper($method); |
||||
|
||||
$config = $configALL[$site]; |
||||
self::$cookies = $config['cookie']; |
||||
self::$userAgent = isset($config['userAgent']) && $config['userAgent'] ? $config['userAgent'] : $configALL['default']['userAgent']; |
||||
self::$clients = isset($config['clients']) && $config['clients'] ? $config['clients'] : $configALL['default']['clients']; |
||||
self::$workingMode = isset($config['workingMode']) && $config['workingMode'] ? $config['workingMode'] : 0; |
||||
$watch = isset($config['watch']) && $config['watch'] ? $config['watch'] : $configALL['default']['watch']; |
||||
self::$watch = rtrim($watch,'/') . DS; |
||||
self::$torrentDir = TORRENT_PATH . $site . DS; |
||||
// 建立目录 |
||||
IFile::mkdir(self::$torrentDir); |
||||
|
||||
self::links(); |
||||
} |
||||
|
||||
/** |
||||
* 连接远端RPC服务器 |
||||
* |
||||
* @param string |
||||
* @return array |
||||
*/ |
||||
public static function links() |
||||
{ |
||||
if(self::$workingMode === 1 && empty(self::$links)){ |
||||
foreach ( self::$clients as $k => $v ){ |
||||
// 跳过未配置的客户端 |
||||
if (empty($v['username']) || empty( $v['password'])) { |
||||
unset(self::$clients[$k]); |
||||
echo "clients_".$k." 用户名或密码未配置,已跳过 \n\n"; |
||||
continue; |
||||
} |
||||
try |
||||
{ |
||||
switch($v['type']){ |
||||
case 'transmission': |
||||
self::$links[$k]['rpc'] = new TransmissionRPC($v['host'], $v['username'], $v['password']); |
||||
$result = self::$links[$k]['rpc']->sstats(); |
||||
print $v['type'].':'.$v['host']." Rpc连接成功 [{$result->result}] \n"; |
||||
break; |
||||
case 'qBittorrent': |
||||
self::$links[$k]['rpc'] = new qBittorrent($v['host'], $v['username'], $v['password']); |
||||
$result = self::$links[$k]['rpc']->appVersion(); |
||||
print $v['type'].':'.$v['host']." Rpc连接成功 [{$result}] \n"; |
||||
break; |
||||
case 'uTorrent': |
||||
self::$links[$k]['rpc'] = new uTorrent($v['host'], $v['username'], $v['password']); |
||||
$result = self::$links[$k]['rpc']->getBuild(); |
||||
print $v['type'].':'.$v['host']." Rpc连接 [{$result}] \n"; |
||||
break; |
||||
default: |
||||
echo '[ERROR] '.$v['type']; |
||||
exit(1); |
||||
break; |
||||
} |
||||
self::$links[$k]['type'] = $v['type']; |
||||
self::$links[$k]['downloadDir'] = $v['downloadDir']; |
||||
} catch (Exception $e) { |
||||
echo '[ERROR] ' . $e->getMessage() . PHP_EOL; |
||||
exit(1); |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* @brief 添加下载任务 |
||||
* @param string $torrent 种子元数据 |
||||
* @param string $save_path 保存路径 |
||||
* @return bool |
||||
*/ |
||||
public static function add($torrent, $save_path = '', $extra_options = array()) |
||||
{ |
||||
switch( (int)self::$workingMode ){ |
||||
case 0: // watch默认工作模式 |
||||
// 复制到watch目录 |
||||
copy($torrent,$save_path); |
||||
if(is_file($save_path)){ |
||||
print "********watch模式,下载任务添加成功 \n\n"; |
||||
return true; |
||||
}else { |
||||
print "-----watch模式,下载任务添加失败!!! \n\n"; |
||||
} |
||||
break; |
||||
case 1: //负载均衡模式 |
||||
try |
||||
{ |
||||
$is_url = false; |
||||
if( (strpos($torrent,'http://')===0) || (strpos($torrent,'https://')===0) || (strpos($torrent,'magnet:?xt=urn:btih:')===0) ){ |
||||
$is_url = true; |
||||
} |
||||
// 负载均衡 |
||||
$rpcKey = self::$RPC_Key; |
||||
echo '选中:负载均衡'.$rpcKey."\n"; |
||||
self::rpcSelect(); |
||||
// 调试 |
||||
#p($result); |
||||
// 下载服务器类型 判断 |
||||
$type = self::$links[$rpcKey]['type']; |
||||
switch($type){ |
||||
case 'transmission': |
||||
if( $is_url ){ |
||||
echo 'add'; |
||||
$result = self::$links[$rpcKey]['rpc']->add( $torrent, self::$links[$rpcKey]['downloadDir'], $extra_options ); // 种子URL添加下载任务 |
||||
}else{ |
||||
echo 'add_metainfo'; |
||||
$result = self::$links[$rpcKey]['rpc']->add_metainfo( $torrent, self::$links[$rpcKey]['downloadDir'], $extra_options ); // 种子文件添加下载任务 |
||||
} |
||||
$id = $name = ''; |
||||
if( isset($result->arguments->torrent_duplicate) ){ |
||||
$id = $result->arguments->torrent_duplicate->id; |
||||
$name = $result->arguments->torrent_duplicate->name; |
||||
}elseif( isset($result->arguments->torrent_added) ){ |
||||
$id = $result->arguments->torrent_added->id; |
||||
$name = $result->arguments->torrent_added->name; |
||||
} |
||||
if(!$id){ |
||||
print "-----RPC添加种子任务,失败 [{$result->result}] \n\n"; |
||||
}else{ |
||||
print "********RPC添加下载任务成功 [{$result->result}] (id=$id) \n\n"; |
||||
// 新添加的任务,开始 |
||||
self::$links[$rpcKey]['rpc']->start( $id ); |
||||
return true; |
||||
} |
||||
break; |
||||
case 'qBittorrent': |
||||
if( $is_url ){ |
||||
echo 'add'; |
||||
$result = self::$links[$rpcKey]['rpc']->add( $torrent, self::$links[$rpcKey]['downloadDir'], $extra_options ); // 种子URL添加下载任务 |
||||
}else{ |
||||
echo 'add_metainfo'; |
||||
$result = self::$links[$rpcKey]['rpc']->add_metainfo( $torrent, self::$links[$rpcKey]['downloadDir'], $extra_options ); // 种子文件添加下载任务 |
||||
} |
||||
if ($result === 'Ok.') { |
||||
print "********RPC添加下载任务成功 [{$result}] \n\n"; |
||||
return true; |
||||
} else { |
||||
print "-----RPC添加种子任务,失败 [{$result}] \n\n"; |
||||
} |
||||
break; |
||||
default: |
||||
echo '[ERROR] '.$type; |
||||
break; |
||||
} |
||||
} catch (Exception $e) { |
||||
die('[ERROR] ' . $e->getMessage() . PHP_EOL); |
||||
} |
||||
break; |
||||
case 2: |
||||
echo "\n\n"; |
||||
# 暂未开放 |
||||
break; |
||||
default: |
||||
echo "\n\n"; |
||||
break; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 负载均衡 选择算法 |
||||
* |
||||
* @param |
||||
* @return |
||||
*/ |
||||
public static function rpcSelect() |
||||
{ |
||||
$clientsConut = count(self::$clients); |
||||
if( $clientsConut > 1 ){ |
||||
if( $clientsConut > (self::$RPC_Key+1) ){ |
||||
self::$RPC_Key++; |
||||
}else{ |
||||
self::$RPC_Key = 0; |
||||
} |
||||
} |
||||
} |
||||
/** |
||||
* @brief 种子处理函数 |
||||
* @param array $data 种子数组 |
||||
* Array |
||||
( |
||||
[id] => 118632 |
||||
[h1] => CCTV5+ 2019 ATP Men's Tennis Final 20191115B HDTV 1080i H264-HDxxx |
||||
[title] => 央视体育赛事频道 2019年ATP男子网球年终总决赛 单打小组赛 纳达尔VS西西帕斯 20191115[优惠剩余时间:4时13分] |
||||
[details] => https://XXX.me/details.php?id=118632 |
||||
[download] => https://XXX.me/download.php?id=118632 |
||||
[filename] => 118632.torrent |
||||
[type] => 0 |
||||
[sticky] => 1 |
||||
[time] => Array |
||||
( |
||||
[0] => "2019-11-16 20:41:53">4时13分 |
||||
[1] => "2019-11-16 14:41:53">1时<br />46分 |
||||
) |
||||
[comments] => 0 |
||||
[size] => 5232.64MB |
||||
[seeders] => 69 |
||||
[leechers] => 10 |
||||
[completed] => 93 |
||||
[percentage] => 100% |
||||
[owner] => 匿名 |
||||
) |
||||
* @return |
||||
*/ |
||||
public static function call($data = array()) |
||||
{ |
||||
foreach ($data as $key => $value) { |
||||
// 控制台打印 |
||||
echo '主标题:'.$value['h1']."\n"; |
||||
echo '副标题:'.$value['title']."\n"; |
||||
echo '详情页:'.$value['details']."\n"; |
||||
if ( $value['type'] != 0 ) { |
||||
echo "-----非免费,已忽略! \n\n"; |
||||
continue; |
||||
} |
||||
if ( isset($value['hr']) && ($value['hr'] == 1) ) { |
||||
echo "-----HR种子,已忽略! \n\n"; |
||||
continue; |
||||
} |
||||
// 下载任务的可选参数 |
||||
$extra_options = array(); |
||||
// 保存的文件名 |
||||
$filename = $value['id'] . '.torrent'; |
||||
// 默认watch工作模式,复制到此目录 |
||||
$to = self::$watch . $filename; |
||||
// 种子完整存放路径 |
||||
$torrentFile = self::$torrentDir . $filename; |
||||
if(is_file($torrentFile)){ |
||||
$fileSize = filesize($torrentFile); //失败会返回false 或 0(0代表上次下载失败) |
||||
if ( !empty($fileSize) ) { |
||||
//种子已经存在 |
||||
echo '-----存在旧种子:'.$filename."\n\n"; |
||||
continue; |
||||
} |
||||
// 删除下载错误的文件 |
||||
IFile::unlink($torrentFile); |
||||
} |
||||
|
||||
// 调用过滤函数 |
||||
$isFilter = filter(self::$site, $value); |
||||
if ( is_string( $isFilter ) ) { |
||||
echo "-----" .$isFilter. "\n\n"; |
||||
continue; |
||||
} |
||||
//种子不存在 |
||||
echo '正在下载新种子... '.$value['download']." \n"; |
||||
// 创建文件、下载种子以二进制写入 |
||||
$content = ''; |
||||
$content = download($value['download'], self::$cookies, self::$userAgent, self::$method); |
||||
#p($content); |
||||
// 文件句柄 |
||||
$resource = fopen($torrentFile, "wb"); |
||||
// 成功:返回写入字节数,失败返回false |
||||
$worldsnum = fwrite($resource, $content); |
||||
// 关闭 |
||||
fclose($resource); |
||||
// 判断 |
||||
if(is_bool($worldsnum)){ |
||||
print "种子下载失败!!! \n\n"; |
||||
IFile::unlink($torrentFile); |
||||
continue; |
||||
}else{ |
||||
print "成功下载种子" . $filename . ',共计:' . $worldsnum . "字节 \n"; |
||||
sleep(mt_rand(2,10)); |
||||
$ret = false; |
||||
$rpcKey = self::$RPC_Key; |
||||
switch( (int)self::$workingMode ){ |
||||
case 0: //默认工作模式 |
||||
$ret = self::add($torrentFile, $to); |
||||
break; |
||||
case 1: //负载均衡模式 |
||||
$type = self::$links[$rpcKey]['type']; |
||||
// 下载服务器类型 |
||||
switch ($type) { |
||||
case 'transmission': |
||||
# code... |
||||
break; |
||||
case 'qBittorrent': |
||||
$extra_options['name'] = 'torrents'; |
||||
$extra_options['filename'] = $filename; |
||||
$extra_options['autoTMM'] = 'false'; //关闭自动种子管理 |
||||
break; |
||||
default: |
||||
# code... |
||||
break; |
||||
} |
||||
// 种子文件添加下载任务 |
||||
$ret = self::add($content, $to, $extra_options); |
||||
break; |
||||
case 2: |
||||
echo "\n\n"; |
||||
# 暂未开放 |
||||
break; |
||||
default: |
||||
echo "\n\n"; |
||||
break; |
||||
} |
||||
global $configALL; |
||||
if( isset($configALL['iyuu.cn']) && ($ret === true) ){ |
||||
send(self::$site, $value); |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
} |
@ -0,0 +1,708 @@ |
||||
<?php |
||||
/** |
||||
* Transmission bittorrent client/daemon RPC communication class |
||||
* Copyright (C) 2010 Johan Adriaans <johan.adriaans@gmail.com>, |
||||
* Bryce Chidester <bryce@cobryce.com> |
||||
* |
||||
* This program is free software: you can redistribute it and/or modify |
||||
* it under the terms of the GNU General Public License as published by |
||||
* the Free Software Foundation, either version 3 of the License, or |
||||
* (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
*/ |
||||
|
||||
/** |
||||
* PHP version specific information |
||||
* version_compare() (PHP 4 >= 4.1.0, PHP 5) |
||||
* ctype_digit() (PHP 4 >= 4.0.4, PHP 5) |
||||
* stream_context_create (PHP 4 >= 4.3.0, PHP 5) |
||||
* PHP Class support (PHP 5) (PHP 4 might work, untested) |
||||
*/ |
||||
|
||||
/** |
||||
* A friendly little version check... |
||||
*/ |
||||
if ( version_compare( PHP_VERSION, TransmissionRPC::MIN_PHPVER, '<' ) ) |
||||
die( "The TransmissionRPC class requires PHP version {TransmissionRPC::TRANSMISSIONRPC_MIN_PHPVER} or above." . PHP_EOL ); |
||||
|
||||
/** |
||||
* Transmission bittorrent client/daemon RPC communication class |
||||
* |
||||
* Usage example: |
||||
* <code> |
||||
* $rpc = new TransmissionRPC($rpc_url); |
||||
* $result = $rpc->add_file( $url_or_path_to_torrent, $target_folder ); |
||||
* </code> |
||||
* |
||||
*/ |
||||
class TransmissionRPC |
||||
{ |
||||
/** |
||||
* User agent used in all http communication |
||||
*/ |
||||
const HTTP_UA = 'TransmissionRPC for PHP/0.3'; |
||||
|
||||
/** |
||||
* Minimum PHP version required |
||||
* 5.2.10 implemented the required http stream ignore_errors option |
||||
*/ |
||||
const MIN_PHPVER = '5.2.10'; |
||||
|
||||
/** |
||||
* The URL to the bittorent client you want to communicate with |
||||
* the port (default: 9091) can be set in you Transmission preferences |
||||
* @var string |
||||
*/ |
||||
public $url = ''; |
||||
|
||||
/** |
||||
* If your Transmission RPC requires authentication, supply username here |
||||
* @var string |
||||
*/ |
||||
public $username = ''; |
||||
|
||||
/** |
||||
* If your Transmission RPC requires authentication, supply password here |
||||
* @var string |
||||
*/ |
||||
public $password = ''; |
||||
|
||||
/** |
||||
* Return results as an array, or an object (default) |
||||
* @var bool |
||||
*/ |
||||
public $return_as_array = false; |
||||
|
||||
/** |
||||
* Print debugging information, default is off |
||||
* @var bool |
||||
*/ |
||||
public $debug = false; |
||||
|
||||
/** |
||||
* Transmission RPC version |
||||
* @var int |
||||
*/ |
||||
protected $rpc_version = 0; |
||||
|
||||
/** |
||||
* Transmission uses a session id to prevent CSRF attacks |
||||
* @var string |
||||
*/ |
||||
protected $session_id = ''; |
||||
|
||||
/** |
||||
* Default values for stream context |
||||
* @var array |
||||
*/ |
||||
private $default_context_opts = array( 'http' => array( |
||||
'user_agent' => self::HTTP_UA, |
||||
'timeout' => '60', // Don't want to be too slow |
||||
'ignore_errors' => true, // Leave the error parsing/handling to the code |
||||
) |
||||
); |
||||
|
||||
/** |
||||
* Constants for torrent status |
||||
*/ |
||||
const TR_STATUS_STOPPED = 0; |
||||
const TR_STATUS_CHECK_WAIT = 1; |
||||
const TR_STATUS_CHECK = 2; |
||||
const TR_STATUS_DOWNLOAD_WAIT = 3; |
||||
const TR_STATUS_DOWNLOAD = 4; |
||||
const TR_STATUS_SEED_WAIT = 5; |
||||
const TR_STATUS_SEED = 6; |
||||
|
||||
const RPC_LT_14_TR_STATUS_CHECK_WAIT = 1; |
||||
const RPC_LT_14_TR_STATUS_CHECK = 2; |
||||
const RPC_LT_14_TR_STATUS_DOWNLOAD = 4; |
||||
const RPC_LT_14_TR_STATUS_SEED = 8; |
||||
const RPC_LT_14_TR_STATUS_STOPPED = 16; |
||||
|
||||
/** |
||||
* Start one or more torrents |
||||
* |
||||
* @param int|array ids A list of transmission torrent ids |
||||
*/ |
||||
public function start ( $ids ) |
||||
{ |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $ids to an array if only a single id was passed |
||||
$request = array( "ids" => $ids ); |
||||
return $this->request( "torrent-start", $request ); |
||||
} |
||||
|
||||
/** |
||||
* Stop one or more torrents |
||||
* |
||||
* @param int|array ids A list of transmission torrent ids |
||||
*/ |
||||
public function stop ( $ids ) |
||||
{ |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $ids to an array if only a single id was passed |
||||
$request = array( "ids" => $ids ); |
||||
return $this->request( "torrent-stop", $request ); |
||||
} |
||||
|
||||
/** |
||||
* Reannounce one or more torrents |
||||
* |
||||
* @param int|array ids A list of transmission torrent ids |
||||
*/ |
||||
public function reannounce ( $ids ) |
||||
{ |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $ids to an array if only a single id was passed |
||||
$request = array( "ids" => $ids ); |
||||
return $this->request( "torrent-reannounce", $request ); |
||||
} |
||||
|
||||
/** |
||||
* Verify one or more torrents |
||||
* |
||||
* @param int|array ids A list of transmission torrent ids |
||||
*/ |
||||
public function verify ( $ids ) |
||||
{ |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $ids to an array if only a single id was passed |
||||
$request = array( "ids" => $ids ); |
||||
return $this->request( "torrent-verify", $request ); |
||||
} |
||||
|
||||
/** |
||||
* Get information on torrents in transmission, if the ids parameter is |
||||
* empty all torrents will be returned. The fields array can be used to return certain |
||||
* fields. Default fields are: "id", "name", "status", "doneDate", "haveValid", "totalSize". |
||||
* See https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt for available fields |
||||
* |
||||
* @param array fields An array of return fields |
||||
* @param int|array ids A list of transmission torrent ids |
||||
* |
||||
Request: |
||||
{ |
||||
"arguments": { |
||||
"fields": [ "id", "name", "totalSize" ], |
||||
"ids": [ 7, 10 ] |
||||
}, |
||||
"method": "torrent-get", |
||||
"tag": 39693 |
||||
} |
||||
|
||||
Response: |
||||
{ |
||||
"arguments": { |
||||
"torrents": [ |
||||
{ |
||||
"id": 10, |
||||
"name": "Fedora x86_64 DVD", |
||||
"totalSize": 34983493932, |
||||
}, |
||||
{ |
||||
"id": 7, |
||||
"name": "Ubuntu x86_64 DVD", |
||||
"totalSize", 9923890123, |
||||
} |
||||
] |
||||
}, |
||||
"result": "success", |
||||
"tag": 39693 |
||||
} |
||||
*/ |
||||
public function get ( $ids = array(), $fields = array() ) |
||||
{ |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $ids to an array if only a single id was passed |
||||
if ( count( $fields ) == 0 ) $fields = array( "id", "name", "status", "doneDate", "haveValid", "totalSize" ); // Defaults |
||||
$request = array( |
||||
"fields" => $fields, |
||||
"ids" => $ids |
||||
); |
||||
return $this->request( "torrent-get", $request ); |
||||
} |
||||
|
||||
/** |
||||
* Set properties on one or more torrents, available fields are: |
||||
* "bandwidthPriority" | number this torrent's bandwidth tr_priority_t |
||||
* "downloadLimit" | number maximum download speed (in K/s) |
||||
* "downloadLimited" | boolean true if "downloadLimit" is honored |
||||
* "files-wanted" | array indices of file(s) to download |
||||
* "files-unwanted" | array indices of file(s) to not download |
||||
* "honorsSessionLimits" | boolean true if session upload limits are honored |
||||
* "ids" | array torrent list, as described in 3.1 |
||||
* "location" | string new location of the torrent's content |
||||
* "peer-limit" | number maximum number of peers |
||||
* "priority-high" | array indices of high-priority file(s) |
||||
* "priority-low" | array indices of low-priority file(s) |
||||
* "priority-normal" | array indices of normal-priority file(s) |
||||
* "seedRatioLimit" | double session seeding ratio |
||||
* "seedRatioMode" | number which ratio to use. See tr_ratiolimit |
||||
* "uploadLimit" | number maximum upload speed (in K/s) |
||||
* "uploadLimited" | boolean true if "uploadLimit" is honored |
||||
* See https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt for more information |
||||
* |
||||
* @param array arguments An associative array of arguments to set |
||||
* @param int|array ids A list of transmission torrent ids |
||||
*/ |
||||
public function set ( $ids = array(), $arguments = array() ) |
||||
{ |
||||
// See https://github.com/transmission/transmission/blob/2.9x/extras/rpc-spec.txt for available fields |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $ids to an array if only a single id was passed |
||||
if ( !isset( $arguments['ids'] ) ) $arguments['ids'] = $ids; // Any $ids given in $arguments overrides the method parameter |
||||
return $this->request( "torrent-set", $arguments ); |
||||
} |
||||
|
||||
/** |
||||
* Add a new torrent |
||||
* |
||||
* Available extra options: |
||||
* key | value type & description |
||||
* ---------------------+------------------------------------------------- |
||||
* "download-dir" | string path to download the torrent to |
||||
* "filename" | string filename or URL of the .torrent file |
||||
* "metainfo" | string base64-encoded .torrent content |
||||
* "paused" | boolean if true, don't start the torrent |
||||
* "peer-limit" | number maximum number of peers |
||||
* "bandwidthPriority" | number torrent's bandwidth tr_priority_t |
||||
* "files-wanted" | array indices of file(s) to download |
||||
* "files-unwanted" | array indices of file(s) to not download |
||||
* "priority-high" | array indices of high-priority file(s) |
||||
* "priority-low" | array indices of low-priority file(s) |
||||
* "priority-normal" | array indices of normal-priority file(s) |
||||
* |
||||
* Either "filename" OR "metainfo" MUST be included. |
||||
* All other arguments are optional. |
||||
* |
||||
* @param torrent_location The URL or path to the torrent file |
||||
* @param save_path Folder to save torrent in |
||||
* @param extra options Optional extra torrent options |
||||
*/ |
||||
public function add_file ( $torrent_location, $save_path = '', $extra_options = array() ) |
||||
{ |
||||
if(!empty($save_path)) $extra_options['download-dir'] = $save_path; |
||||
$extra_options['filename'] = $torrent_location; |
||||
|
||||
return $this->request( "torrent-add", $extra_options ); |
||||
} |
||||
|
||||
/** |
||||
* Add a torrent using the raw torrent data |
||||
* |
||||
* @param torrent_metainfo The raw, unencoded contents (metainfo) of a torrent |
||||
* @param save_path Folder to save torrent in |
||||
* @param extra options Optional extra torrent options |
||||
*/ |
||||
public function add_metainfo ( $torrent_metainfo, $save_path = '', $extra_options = array() ) |
||||
{ |
||||
$extra_options['download-dir'] = $save_path; |
||||
$extra_options['metainfo'] = base64_encode( $torrent_metainfo ); |
||||
|
||||
return $this->request( "torrent-add", $extra_options ); |
||||
} |
||||
|
||||
/* Add a new torrent using a file path or a URL (For backwards compatibility) |
||||
* @param torrent_location The URL or path to the torrent file |
||||
* @param save_path Folder to save torrent in |
||||
* @param extra options Optional extra torrent options |
||||
*/ |
||||
public function add ( $torrent_location, $save_path = '', $extra_options = array() ) |
||||
{ |
||||
return $this->add_file( $torrent_location, $save_path, $extra_options ); |
||||
} |
||||
|
||||
/** |
||||
* Remove torrent from transmission |
||||
* |
||||
* @param bool delete_local_data Also remove local data? |
||||
* @param int|array ids A list of transmission torrent ids |
||||
*/ |
||||
public function remove ( $ids, $delete_local_data = false ) |
||||
{ |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $ids to an array if only a single id was passed |
||||
$request = array( |
||||
"ids" => $ids, |
||||
"delete-local-data" => $delete_local_data |
||||
); |
||||
return $this->request( "torrent-remove", $request ); |
||||
} |
||||
|
||||
/** |
||||
* Move local storage location |
||||
* |
||||
* @param int|array ids A list of transmission torrent ids |
||||
* @param string target_location The new storage location |
||||
* @param string move_existing_data Move existing data or scan new location for available data |
||||
*/ |
||||
public function move ( $ids, $target_location, $move_existing_data = true ) |
||||
{ |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $ids to an array if only a single id was passed |
||||
$request = array( |
||||
"ids" => $ids, |
||||
"location" => $target_location, |
||||
"move" => $move_existing_data |
||||
); |
||||
return $this->request( "torrent-set-location", $request ); |
||||
} |
||||
|
||||
/** |
||||
* 3.7. Renaming a Torrent's Path |
||||
* |
||||
* Method name: "torrent-rename-path" |
||||
* |
||||
* For more information on the use of this function, see the transmission.h |
||||
* documentation of tr_torrentRenamePath(). In particular, note that if this |
||||
* call succeeds you'll want to update the torrent's "files" and "name" field |
||||
* with torrent-get. |
||||
* |
||||
* Request arguments: |
||||
* |
||||
* string | value type & description |
||||
* ---------------------------------+------------------------------------------------- |
||||
* "ids" | array the torrent torrent list, as described in 3.1 |
||||
* | (must only be 1 torrent) |
||||
* "path" | string the path to the file or folder that will be renamed |
||||
* "name" | string the file or folder's new name |
||||
|
||||
* Response arguments: "path", "name", and "id", holding the torrent ID integer |
||||
* |
||||
* @param int|array ids A 1-element list of transmission torrent ids |
||||
* @param string path The path to the file or folder that will be renamed |
||||
* @param string name The file or folder's new name |
||||
*/ |
||||
public function rename ( $ids, $path, $name ) |
||||
{ |
||||
if ( !is_array( $ids ) ) $ids = array( $ids ); // Convert $id to an array if only a single id was passed |
||||
if ( count( $ids ) !== 1 ) { |
||||
throw new TransmissionRPCException( 'A single id is accepted', TransmissionRPCException::E_INVALIDARG ); |
||||
} |
||||
|
||||
$request = array( |
||||
"ids" => $ids, |
||||
"path" => $path, |
||||
"name" => $name |
||||
); |
||||
return $this->request( "torrent-rename-path", $request ); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Retrieve session statistics |
||||
* |
||||
* @returns array of statistics |
||||
*/ |
||||
public function sstats ( ) |
||||
{ |
||||
return $this->request( "session-stats", array() ); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve all session variables |
||||
* |
||||
* @returns array of session information |
||||
*/ |
||||
public function sget ( ) |
||||
{ |
||||
return $this->request( "session-get", array() ); |
||||
} |
||||
|
||||
/** |
||||
* Set session variable(s) |
||||
* |
||||
* @param array of session variables to set |
||||
*/ |
||||
public function sset ( $arguments ) |
||||
{ |
||||
return $this->request( "session-set", $arguments ); |
||||
} |
||||
|
||||
/** |
||||
* Return the interpretation of the torrent status |
||||
* |
||||
* @param int The integer "torrent status" |
||||
* @returns string The translated meaning |
||||
*/ |
||||
public function getStatusString ( $intstatus ) |
||||
{ |
||||
if($this->rpc_version < 14){ |
||||
if( $intstatus == self::RPC_LT_14_TR_STATUS_CHECK_WAIT ) |
||||
return "Waiting to verify local files"; |
||||
if( $intstatus == self::RPC_LT_14_TR_STATUS_CHECK ) |
||||
return "Verifying local files"; |
||||
if( $intstatus == self::RPC_LT_14_TR_STATUS_DOWNLOAD ) |
||||
return "Downloading"; |
||||
if( $intstatus == self::RPC_LT_14_TR_STATUS_SEED ) |
||||
return "Seeding"; |
||||
if( $intstatus == self::RPC_LT_14_TR_STATUS_STOPPED ) |
||||
return "Stopped"; |
||||
}else{ |
||||
if( $intstatus == self::TR_STATUS_CHECK_WAIT ) |
||||
return "Waiting to verify local files"; |
||||
if( $intstatus == self::TR_STATUS_CHECK ) |
||||
return "Verifying local files"; |
||||
if( $intstatus == self::TR_STATUS_DOWNLOAD ) |
||||
return "Downloading"; |
||||
if( $intstatus == self::TR_STATUS_SEED ) |
||||
return "Seeding"; |
||||
if( $intstatus == self::TR_STATUS_STOPPED ) |
||||
return "Stopped"; |
||||
if( $intstatus == self::TR_STATUS_SEED_WAIT ) |
||||
return "Queued for seeding"; |
||||
if( $intstatus == self::TR_STATUS_DOWNLOAD_WAIT ) |
||||
return "Queued for download"; |
||||
} |
||||
return "Unknown"; |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
* Here be dragons (Internal methods) |
||||
*/ |
||||
|
||||
|
||||
|
||||
/** |
||||
* Clean up the request array. Removes any empty fields from the request |
||||
* |
||||
* @param array array The request associative array to clean |
||||
* @returns array The cleaned array |
||||
*/ |
||||
protected function cleanRequestData ( $array ) |
||||
{ |
||||
if ( !is_array( $array ) || count( $array ) == 0 ) return null; // Nothing to clean |
||||
setlocale( LC_NUMERIC, 'en_US.utf8' ); // Override the locale - if the system locale is wrong, then 12.34 will encode as 12,34 which is invalid JSON |
||||
foreach ( $array as $index => $value ) |
||||
{ |
||||
if( is_object( $value ) ) $array[$index] = $value->toArray(); // Convert objects to arrays so they can be JSON encoded |
||||
if( is_array( $value ) ) $array[$index] = $this->cleanRequestData( $value ); // Recursion |
||||
if( empty( $value ) && $value !== 0 ) // Remove empty members |
||||
{ |
||||
unset( $array[$index] ); |
||||
continue; // Skip the rest of the tests - they may re-add the element. |
||||
} |
||||
if( is_numeric( $value ) ) $array[$index] = $value+0; // Force type-casting for proper JSON encoding (+0 is a cheap way to maintain int/float/etc) |
||||
if( is_bool( $value ) ) $array[$index] = ( $value ? 1 : 0); // Store boolean values as 0 or 1 |
||||
if( is_string( $value ) ) { |
||||
if ( mb_detect_encoding($value,"auto") !== 'UTF-8' ) { |
||||
$array[$index] = mb_convert_encoding($value, "UTF-8"); |
||||
//utf8_encode( $value ); // Make sure all data is UTF-8 encoded for Transmission |
||||
} |
||||
} |
||||
} |
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Clean up the result object. Replaces all minus(-) characters in the object properties with underscores |
||||
* and converts any object with any all-digit property names to an array. |
||||
* |
||||
* @param object The request result to clean |
||||
* @returns array The cleaned object |
||||
*/ |
||||
protected function cleanResultObject ( $object ) |
||||
{ |
||||
// Prepare and cast object to array |
||||
$return_as_array = false; |
||||
$array = $object; |
||||
if ( !is_array( $array ) ) $array = (array) $array; |
||||
foreach ( $array as $index => $value ) |
||||
{ |
||||
if( is_array( $array[$index] ) || is_object( $array[$index] ) ) |
||||
{ |
||||
$array[$index] = $this->cleanResultObject( $array[$index] ); // Recursion |
||||
} |
||||
if ( strstr( $index, '-' ) ) |
||||
{ |
||||
$valid_index = str_replace( '-', '_', $index ); |
||||
$array[$valid_index] = $array[$index]; |
||||
unset( $array[$index] ); |
||||
$index = $valid_index; |
||||
} |
||||
// Might be an array, check index for digits, if so, an array should be returned |
||||
if ( ctype_digit( (string) $index ) ) { $return_as_array = true; } |
||||
if ( empty( $value ) ) unset( $array[$index] ); |
||||
} |
||||
// Return array cast to object |
||||
return $return_as_array ? $array : (object) $array; |
||||
} |
||||
|
||||
/** |
||||
* 执行 rpc 请求 |
||||
* |
||||
* @param $method 请求类型/方法, 详见 $this->allowMethods |
||||
* @param array $arguments 附加参数, 可选 |
||||
* @return mixed |
||||
*/ |
||||
protected function request($method, $arguments = array()) |
||||
{ |
||||
// Check the parameters |
||||
if ( !is_scalar( $method ) ) |
||||
throw new TransmissionRPCException( 'Method name has no scalar value', TransmissionRPCException::E_INVALIDARG ); |
||||
if ( !is_array( $arguments ) ) |
||||
throw new TransmissionRPCException( 'Arguments must be given as array', TransmissionRPCException::E_INVALIDARG ); |
||||
|
||||
$arguments = $this->cleanRequestData( $arguments ); // Sanitize input |
||||
|
||||
// Grab the X-Transmission-Session-Id if we don't have it already |
||||
if( !$this->session_id ) |
||||
if( !$this->GetSessionID() ) |
||||
throw new TransmissionRPCException( 'Unable to acquire X-Transmission-Session-Id', TransmissionRPCException::E_SESSIONID ); |
||||
|
||||
$data = array( |
||||
'method' => $method, |
||||
'arguments' => $arguments |
||||
); |
||||
|
||||
$header = array( |
||||
'Content-Type: application/json', |
||||
'Authorization: Basic '.base64_encode(sprintf("%s:%s", $this->username, $this->password)), |
||||
'X-Transmission-Session-Id: '.$this->session_id |
||||
); |
||||
|
||||
$ch = curl_init(); |
||||
curl_setopt($ch, CURLOPT_URL, $this->url); |
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $header); |
||||
curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); |
||||
curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password); |
||||
curl_setopt($ch, CURLOPT_HEADER, false); |
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
||||
curl_setopt($ch, CURLOPT_POST, true); |
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); |
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60); |
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 600); |
||||
$content = curl_exec($ch); |
||||
curl_close($ch); |
||||
|
||||
if (!$content) $content = json_encode(array('result' => 'failed')); |
||||
return $this->return_as_array ? json_decode( $content, true ) : $this->cleanResultObject( json_decode( $content ) ); // Return the sanitized result |
||||
} |
||||
/** |
||||
* Performs an empty GET on the Transmission RPC to get the X-Transmission-Session-Id |
||||
* and store it in $this->session_id |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function GetSessionID() |
||||
{ |
||||
if( !$this->url ) |
||||
throw new TransmissionRPCException( "Class must be initialized before GetSessionID() can be called.", TransmissionRPCException::E_INVALIDARG ); |
||||
|
||||
// Setup the context |
||||
$contextopts = $this->default_context_opts; // Start with the defaults |
||||
|
||||
// Make sure it's blank/empty (reset) |
||||
$this->session_id = null; |
||||
|
||||
// Setup authentication (if provided) |
||||
if ( $this->username && $this->password ) |
||||
$contextopts['http']['header'] = sprintf( "Authorization: Basic %s\r\n", base64_encode( $this->username.':'.$this->password ) ); |
||||
|
||||
if( $this->debug ) echo "TRANSMISSIONRPC_DEBUG:: GetSessionID():: Stream context created with options:". |
||||
PHP_EOL . print_r( $contextopts, true ); |
||||
|
||||
$context = stream_context_create( $contextopts ); // Create the context for this request |
||||
if ( ! $fp = @fopen( $this->url, 'r', false, $context ) ) // Open a filepointer to the data, and use fgets to get the result |
||||
throw new TransmissionRPCException( 'Unable to connect to '.$this->url, TransmissionRPCException::E_CONNECTION ); |
||||
|
||||
// Check the response (headers etc) |
||||
$stream_meta = stream_get_meta_data( $fp ); |
||||
fclose( $fp ); |
||||
if( $this->debug ) echo "TRANSMISSIONRPC_DEBUG:: GetSessionID():: Stream meta info: ". |
||||
PHP_EOL . print_r( $stream_meta, true ); |
||||
if( $stream_meta['timed_out'] ) |
||||
throw new TransmissionRPCException( "Timed out connecting to {$this->url}", TransmissionRPCException::E_CONNECTION ); |
||||
if( substr( $stream_meta['wrapper_data'][0], 9, 3 ) == "401" ) |
||||
throw new TransmissionRPCException( "Invalid username/password.", TransmissionRPCException::E_AUTHENTICATION ); |
||||
elseif( substr( $stream_meta['wrapper_data'][0], 9, 3 ) == "409" ) // This is what we're hoping to find |
||||
{ |
||||
// Loop through the returned headers and extract the X-Transmission-Session-Id |
||||
foreach( $stream_meta['wrapper_data'] as $header ) |
||||
{ |
||||
if( strpos( $header, 'X-Transmission-Session-Id: ' ) === 0 ) |
||||
{ |
||||
if( $this->debug ) echo "TRANSMISSIONRPC_DEBUG:: GetSessionID():: Session-Id header: ". |
||||
PHP_EOL . print_r( $header, true ); |
||||
$this->session_id = trim( substr( $header, 27 ) ); |
||||
break; |
||||
} |
||||
} |
||||
if( ! $this->session_id ) { // Didn't find a session_id |
||||
throw new TransmissionRPCException( "Unable to retrieve X-Transmission-Session-Id", TransmissionRPCException::E_SESSIONID ); |
||||
} |
||||
} else { |
||||
throw new TransmissionRPCException( "Unexpected response from Transmission RPC: ".$stream_meta['wrapper_data'][0] ); |
||||
} |
||||
return $this->session_id; |
||||
} |
||||
|
||||
/** |
||||
* Takes the connection parameters |
||||
* |
||||
* TODO: Sanitize username, password, and URL |
||||
* |
||||
* @param string $url |
||||
* @param string $username |
||||
* @param string $password |
||||
*/ |
||||
public function __construct( $url = 'http://localhost:9091/transmission/rpc', $username = null, $password = null, $return_as_array = false ) |
||||
{ |
||||
// server URL |
||||
$this->url = $url; |
||||
|
||||
// Username & password |
||||
$this->username = $username; |
||||
$this->password = $password; |
||||
|
||||
// Get the Transmission RPC_version |
||||
$this->rpc_version = self::sget()->arguments->rpc_version; |
||||
|
||||
// Return As Array |
||||
$this->return_as_array = $return_as_array; |
||||
|
||||
// Reset X-Transmission-Session-Id so we (re)fetch one |
||||
$this->session_id = null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* This is the type of exception the TransmissionRPC class will throw |
||||
*/ |
||||
class TransmissionRPCException extends Exception |
||||
{ |
||||
/** |
||||
* Exception: Invalid arguments |
||||
*/ |
||||
const E_INVALIDARG = -1; |
||||
|
||||
/** |
||||
* Exception: Invalid Session-Id |
||||
*/ |
||||
const E_SESSIONID = -2; |
||||
|
||||
/** |
||||
* Exception: Error while connecting |
||||
*/ |
||||
const E_CONNECTION = -3; |
||||
|
||||
/** |
||||
* Exception: Error 401 returned, unauthorized |
||||
*/ |
||||
const E_AUTHENTICATION = -4; |
||||
|
||||
/** |
||||
* Exception constructor |
||||
*/ |
||||
public function __construct( $message = null, $code = 0, Exception $previous = null ) |
||||
{ |
||||
// PHP version 5.3.0 and above support Exception linking |
||||
if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) |
||||
parent::__construct( $message, $code, $previous ); |
||||
else |
||||
parent::__construct( $message, $code ); |
||||
} |
||||
} |
||||
|
||||
?> |
@ -0,0 +1,288 @@ |
||||
<?php |
||||
use Curl\Curl; |
||||
/** |
||||
* https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation |
||||
*/ |
||||
class qBittorrent |
||||
{ |
||||
private $debug; |
||||
private $url; |
||||
private $api_version; |
||||
private $curl; |
||||
protected $delimiter; |
||||
private $endpoints = [ |
||||
'login' => [ |
||||
'1' => '/login', |
||||
'2' => '/api/v2/auth/login' |
||||
], |
||||
'app_version' => [ |
||||
'1' => '/version/qbittorrent', |
||||
'2' => '/api/v2/app/version' |
||||
], |
||||
'api_version' => [ |
||||
'1' => '/version/api', |
||||
'2' => '/api/v2/app/webapiVersion' |
||||
], |
||||
'build_info' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/app/buildInfo' |
||||
], |
||||
'preferences' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/app/preferences' |
||||
], |
||||
'setPreferences' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/app/setPreferences' |
||||
], |
||||
'defaultSavePath' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/app/defaultSavePath' |
||||
], |
||||
'torrent_list' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/torrents/info' |
||||
], |
||||
'torrent_add' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/torrents/add' |
||||
], |
||||
'torrent_delete' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/torrents/delete' |
||||
], |
||||
'torrent_pause' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/torrents/pause' |
||||
], |
||||
'torrent_resume' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/torrents/resume' |
||||
], |
||||
'set_torrent_location' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/torrents/setLocation' |
||||
], |
||||
'maindata' => [ |
||||
'1' => null, |
||||
'2' => '/api/v2/sync/maindata' |
||||
] |
||||
]; |
||||
|
||||
public function __construct($url='', $username='', $password='', $api_version = 2, $debug = false) |
||||
{ |
||||
$this->debug = $debug; |
||||
$this->url = rtrim($url,'/'); |
||||
$this->username = $username; |
||||
$this->password = $password; |
||||
$this->api_version = $api_version; |
||||
$this->curl = new Curl(); |
||||
$this->curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); // 禁止验证证书 |
||||
$this->curl->setOpt(CURLOPT_SSL_VERIFYHOST, false); // 不检查证书 |
||||
$this->curl->setOpt(CURLOPT_CONNECTTIMEOUT, 60); // 超时 |
||||
$this->curl->setOpt(CURLOPT_TIMEOUT, 600); // 超时 |
||||
// Authenticate and get cookie, else throw exception |
||||
if (!$this->authenticate()) { |
||||
throw new \Exception("Unable to authenticate with Web Api."); |
||||
} |
||||
} |
||||
|
||||
public function appVersion() |
||||
{ |
||||
return $this->getData('app_version'); |
||||
} |
||||
|
||||
public function apiVersion() |
||||
{ |
||||
return $this->getData('api_version'); |
||||
} |
||||
|
||||
public function buildInfo() |
||||
{ |
||||
return $this->getData('build_info'); |
||||
} |
||||
|
||||
public function preferences($data = null) |
||||
{ |
||||
if (!empty($data)) { |
||||
return $this->postData('setPreferences', ['json' => json_encode($data)]); |
||||
} |
||||
|
||||
return $this->getData('preferences'); |
||||
} |
||||
|
||||
public function torrentList() |
||||
{ |
||||
return $this->getData('torrent_list'); |
||||
} |
||||
/** |
||||
* @param array $extra_options |
||||
array( |
||||
'urls' => '', |
||||
'savepath' => '', |
||||
'cookie' => '', |
||||
'category' => '', |
||||
'skip_checking' => true, |
||||
'paused' => true, |
||||
'root_folder' => true, |
||||
) |
||||
* @return array |
||||
*/ |
||||
public function add($torrent_url, $save_path = '', $extra_options = array()) |
||||
{ |
||||
if(!empty($save_path)) $extra_options['savepath'] = $save_path; |
||||
$extra_options['urls'] = $torrent_url; |
||||
#$extra_options['skip_checking'] = 'true'; //跳校验 |
||||
// 关键 上传文件流 multipart/form-data【严格按照api文档编写】 |
||||
$post_data = $this->buildUrls($extra_options); |
||||
#p($post_data); |
||||
// 设置请求头 |
||||
$this->curl->setHeader('Content-Type','multipart/form-data; boundary='.$this->delimiter); |
||||
$this->curl->setHeader('Content-Length',strlen($post_data)); |
||||
return $this->postData('torrent_add', $post_data); |
||||
} |
||||
|
||||
public function add_metainfo($torrent_metainfo, $save_path = '', $extra_options = array()) |
||||
{ |
||||
if(!empty($save_path)) $extra_options['savepath'] = $save_path; |
||||
$extra_options['torrents'] = $torrent_metainfo; |
||||
#$extra_options['skip_checking'] = 'true'; //跳校验 |
||||
// 关键 上传文件流 multipart/form-data【严格按照api文档编写】 |
||||
$post_data = $this->buildData($extra_options); |
||||
#p($post_data); |
||||
// 设置请求头 |
||||
$this->curl->setHeader('Content-Type','multipart/form-data; boundary='.$this->delimiter); |
||||
$this->curl->setHeader('Content-Length',strlen($post_data)); |
||||
return $this->postData('torrent_add', $post_data); |
||||
} |
||||
|
||||
public function torrentDelete($hash='', $deleteFiles = false) |
||||
{ |
||||
return $this->postData('torrent_delete', ['hashes' => $hash, 'deleteFiles' => $deleteFiles ? 'true':'false']); |
||||
} |
||||
|
||||
public function torrentDeleteAll($deleteFiles = false) |
||||
{ |
||||
$torrents = json_decode($this->torrentList()); |
||||
$response = ''; |
||||
foreach ($torrents as $torrent) { |
||||
$response .= $this->torrentDelete($torrent->hash, $deleteFiles); |
||||
} |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
public function torrentPause($hash) |
||||
{ |
||||
return $this->postData('torrent_pause', ['hashes' => $hash]); |
||||
} |
||||
|
||||
public function torrentResume($hash) |
||||
{ |
||||
return $this->postData('torrent_resume', ['hashes' => $hash]); |
||||
} |
||||
|
||||
public function setTorrentLocation($hash, $location) |
||||
{ |
||||
return $this->postData('set_torrent_location', ['hashes' => $hash, 'location' => $location]); |
||||
} |
||||
|
||||
private function getData($endpoint) |
||||
{ |
||||
$this->curl->get($this->url . $this->endpoints[$endpoint][$this->api_version]); |
||||
|
||||
if ($this->debug) { |
||||
var_dump($this->curl->request_headers); |
||||
var_dump($this->curl->response_headers); |
||||
} |
||||
|
||||
if ($this->curl->error) { |
||||
return $this->errorMessage(); |
||||
} |
||||
|
||||
return $this->curl->response; |
||||
} |
||||
|
||||
private function postData($endpoint, $data) |
||||
{ |
||||
$this->curl->post($this->url . $this->endpoints[$endpoint][$this->api_version], $data); |
||||
|
||||
if ($this->debug) { |
||||
var_dump($this->curl->request_headers); |
||||
var_dump($this->curl->response_headers); |
||||
} |
||||
|
||||
if ($this->curl->error) { |
||||
return $this->errorMessage(); |
||||
} |
||||
|
||||
return $this->curl->response; |
||||
} |
||||
|
||||
private function authenticate() |
||||
{ |
||||
$this->curl->post($this->url . $this->endpoints['login'][$this->api_version], [ |
||||
'username' => $this->username, |
||||
'password' => $this->password |
||||
]); |
||||
|
||||
if ($this->debug) { |
||||
var_dump($this->curl->request_headers); |
||||
var_dump($this->curl->response_headers); |
||||
} |
||||
|
||||
// Find authentication cookie and set in curl connection |
||||
foreach ($this->curl->response_headers as $header) { |
||||
if (preg_match('/SID=(\S[^;]+)/', $header, $matches)) { |
||||
$this->curl->setHeader('Cookie', $matches[0]); |
||||
return true; |
||||
} |
||||
}; |
||||
|
||||
return false; |
||||
} |
||||
|
||||
private function errorMessage() |
||||
{ |
||||
return 'Curl Error Code: ' . $this->curl->error_code . ' (' . $this->curl->response . ')'; |
||||
} |
||||
/** |
||||
* 拼接种子urls multipart/form-data |
||||
* https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation#add-new-torrent |
||||
*/ |
||||
private function buildUrls($param){ |
||||
$this->delimiter = uniqid(); |
||||
$eol = "\r\n"; |
||||
$data = ''; |
||||
// 拼接文件流 |
||||
foreach ($param as $name => $content) { |
||||
$data .= "--" . $this->delimiter . $eol; |
||||
$data .= 'Content-Disposition: form-data; name' . '="' .$name. '"' . "\r\n\r\n"; |
||||
$data .= $content . $eol; |
||||
} |
||||
$data .= "--" . $this->delimiter . "--" . $eol; |
||||
return $data; |
||||
} |
||||
/** |
||||
* 拼接种子上传文件流 multipart/form-data |
||||
* https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation#add-new-torrent |
||||
*/ |
||||
private function buildData($param){ |
||||
$this->delimiter = uniqid(); |
||||
$eol = "\r\n"; |
||||
$data = ''; |
||||
$torrents = $param['torrents']; |
||||
unset($param['torrents']); |
||||
// 拼接文件流 |
||||
$data .= "--" . $this->delimiter . $eol |
||||
. 'Content-Disposition: form-data; '; |
||||
foreach ($param as $name => $content) { |
||||
$data.= $name . '="' . $content.'"; '; |
||||
} |
||||
$data .= $eol; |
||||
$data .= 'Content-Type: application/x-bittorrent'."\r\n\r\n"; |
||||
$data .= $torrents . $eol; |
||||
$data .= "--" . $this->delimiter . "--" . $eol; |
||||
return $data; |
||||
} |
||||
} |
@ -0,0 +1,255 @@ |
||||
<?php |
||||
define("UTORRENT_TORRENT_HASH", 0); |
||||
define("UTORRENT_TORRENT_STATUS", 1); |
||||
define("UTORRENT_TORRENT_NAME", 2); |
||||
define("UTORRENT_TORRENT_SIZE", 3); |
||||
define("UTORRENT_TORRENT_PROGRESS", 4); |
||||
define("UTORRENT_TORRENT_DOWNLOADED", 5); |
||||
define("UTORRENT_TORRENT_UPLOADED", 6); |
||||
define("UTORRENT_TORRENT_RATIO", 7); |
||||
define("UTORRENT_TORRENT_UPSPEED", 8); |
||||
define("UTORRENT_TORRENT_DOWNSPEED", 9); |
||||
define("UTORRENT_TORRENT_ETA", 10); |
||||
define("UTORRENT_TORRENT_LABEL", 11); |
||||
define("UTORRENT_TORRENT_PEERS_CONNECTED", 12); |
||||
define("UTORRENT_TORRENT_PEERS_SWARM", 13); |
||||
define("UTORRENT_TORRENT_SEEDS_CONNECTED", 14); |
||||
define("UTORRENT_TORRENT_SEEDS_SWARM", 15); |
||||
define("UTORRENT_TORRENT_AVAILABILITY", 16); |
||||
define("UTORRENT_TORRENT_QUEUE_POSITION", 17); |
||||
define("UTORRENT_TORRENT_REMAINING", 18); |
||||
define("UTORRENT_FILEPRIORITY_HIGH", 3); |
||||
define("UTORRENT_FILEPRIORITY_NORMAL", 2); |
||||
define("UTORRENT_FILEPRIORITY_LOW", 1); |
||||
define("UTORRENT_FILEPRIORITY_SKIP", 0); |
||||
define("UTORRENT_TYPE_INTEGER", 0); |
||||
define("UTORRENT_TYPE_BOOLEAN", 1); |
||||
define("UTORRENT_TYPE_STRING", 2); |
||||
define("UTORRENT_STATUS_STARTED", 1); |
||||
define("UTORRENT_STATUS_CHECKED", 2); |
||||
define("UTORRENT_STATUS_START_AFTER_CHECK", 4); |
||||
|
||||
class uTorrent { |
||||
// class static variables |
||||
private static $base = "%s/gui/%s"; |
||||
|
||||
// member variables |
||||
public $host; |
||||
public $user; |
||||
public $pass; |
||||
|
||||
protected $token; |
||||
protected $guid; |
||||
|
||||
// constructor |
||||
function __construct($host = "", $user = "", $pass = "") { |
||||
$this->host = rtrim($host,'/'); |
||||
$this->user = $user; |
||||
$this->pass = $pass; |
||||
|
||||
if (!$this->getToken()) { |
||||
//handle error here, don't know how to best do this yet |
||||
die('could not get token'); |
||||
} |
||||
} |
||||
|
||||
// performs request |
||||
private function makeRequest($request, $decode = true, $options = array()) { |
||||
$request = preg_replace('/^\?/', '?token='.$this->token . '&', $request); |
||||
|
||||
$ch = curl_init(); |
||||
curl_setopt_array($ch, $options); |
||||
curl_setopt($ch, CURLOPT_URL, sprintf(self::$base, $this->host, $request)); |
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
||||
curl_setopt($ch, CURLOPT_USERPWD, $this->user.":".$this->pass); |
||||
curl_setopt($ch, CURLOPT_COOKIE, "GUID=".$this->guid); |
||||
|
||||
$req = curl_exec($ch); |
||||
curl_close($ch); |
||||
|
||||
return ($decode ? json_decode($req, true) : $req); |
||||
} |
||||
|
||||
// implodes given parameter with glue, whether it is an array or not |
||||
private function paramImplode($glue, $param) { |
||||
return $glue.implode($glue, is_array($param) ? $param : array($param)); |
||||
} |
||||
|
||||
// gets token, returns true on success |
||||
private function getToken() { |
||||
$url = sprintf(self::$base, $this->host, 'token.html'); |
||||
$ch = curl_init(); |
||||
curl_setopt($ch, CURLOPT_URL, $url); |
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
||||
curl_setopt($ch, CURLOPT_USERPWD, $this->user.":".$this->pass); |
||||
curl_setopt($ch, CURLOPT_HEADER, true); |
||||
$output = curl_exec($ch); |
||||
$info = curl_getinfo($ch); |
||||
curl_close($ch); |
||||
|
||||
$headers = substr($output, 0, $info['header_size']); |
||||
|
||||
if (preg_match("@Set-Cookie: GUID=([^;]+);@i", $headers, $matches)) { |
||||
$this->guid = $matches[1]; |
||||
} |
||||
|
||||
if (preg_match('/<div id=\'token\'.+>(.*)<\/div>/', $output, $m)) { |
||||
$this->token = $m[1]; |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
// returns the uTorrent build number |
||||
public function getBuild(){ |
||||
$json = $this->makeRequest("?"); |
||||
return $json['build']; |
||||
} |
||||
|
||||
// returns an array of files for the specified torrent hash |
||||
// TODO: |
||||
// - (when implemented in API) allow multiple hashes to be specified |
||||
public function getFiles($hash) { |
||||
$json = $this->makeRequest("?action=getfiles&hash=".$hash); |
||||
return $json['files']; |
||||
} |
||||
|
||||
// returns an array of all labels |
||||
public function getLabels(){ |
||||
$json = $this->makeRequest("?list=1"); |
||||
return $json['label']; |
||||
} |
||||
|
||||
// returns an array of the properties for the specified torrent hash |
||||
// TODO: |
||||
// - (when implemented in API) allow multiple hashes to be specified |
||||
public function getProperties($hash) { |
||||
$json = $this->makeRequest("?action=getprops&hash=".$hash); |
||||
return $json['props']; |
||||
} |
||||
|
||||
// returns an array of all settings |
||||
public function getSettings() { |
||||
$json = $this->makeRequest("?action=getsettings"); |
||||
return $json['settings']; |
||||
} |
||||
|
||||
// returns an array of all torrent jobs and related information |
||||
public function getTorrents() { |
||||
$json = $this->makeRequest("?list=1"); |
||||
return $json['torrents']; |
||||
} |
||||
|
||||
/** |
||||
* Get all the RSS favourites/filters |
||||
* @return model\Filter[] |
||||
*/ |
||||
public function getRSSFilters() { |
||||
$json = $this->makeRequest("?list=1"); |
||||
$filters = array(); |
||||
foreach ($json['rssfilters'] as $filter) { |
||||
$filters[] = model\Filter::fromData($filter); |
||||
} |
||||
return $filters; |
||||
} |
||||
|
||||
/** |
||||
* Update an RSS filter as retrieved from getRSSFilters |
||||
* @param \uTorrent\model\Filter $filter |
||||
*/ |
||||
public function setRSSFilter(model\Filter $filter) { |
||||
$request = array_merge(array('action' => 'filter-update'), $filter->toParams()); |
||||
return $this->makeRequest('?'.http_build_query($request)); |
||||
} |
||||
|
||||
/** |
||||
* Add a new RSS filter |
||||
* Requires a utorrent > 2.2.1 (not sure which version exactly) |
||||
* @param \uTorrent\model\Filter $filter |
||||
* @return int ID of the new filter |
||||
*/ |
||||
public function addRSSFilter(model\Filter $filter) { |
||||
$filter->filterId = -1; |
||||
$resp = $this->setRSSFilter($filter); |
||||
if (!empty($resp['filter_ident'])) { |
||||
return $resp['filter_ident']; |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
// returns true if WebUI server is online and enabled, false otherwise |
||||
public function is_online() { |
||||
return is_array($this->makeRequest("?")); |
||||
} |
||||
|
||||
// sets the properties for the specified torrent hash |
||||
// TODO: |
||||
// - allow multiple hashes, properties, and values to be set simultaneously |
||||
public function setProperties($hash, $property, $value) { |
||||
$this->makeRequest("?action=setprops&hash=".$hash."&s=".$property."&v=".$value, false); |
||||
} |
||||
|
||||
// sets the priorities for the specified files in the specified torrent hash |
||||
public function setPriority($hash, $files, $priority) { |
||||
$this->makeRequest("?action=setprio&hash=".$hash."&p=".$priority.$this->paramImplode("&f=", $files), false); |
||||
} |
||||
|
||||
// sets the settings |
||||
// TODO: |
||||
// - allow multiple settings and values to be set simultaneously |
||||
public function setSetting($setting, $value) { |
||||
$this->makeRequest("?action=setsetting&s=".$setting."&v=".$value, false); |
||||
} |
||||
|
||||
// add a file to the list |
||||
public function torrentAdd($filename, &$estring = false) { |
||||
$split = explode(":", $filename, 2); |
||||
if (count($split) > 1 && (stristr("|http|https|file|magnet|", "|".$split[0]."|") !== false)) { |
||||
$this->makeRequest("?action=add-url&s=".urlencode($filename), false); |
||||
} |
||||
elseif (file_exists($filename)) { |
||||
$json = $this->makeRequest("?action=add-file", true, array(CURLOPT_POSTFIELDS => array("torrent_file" => "@".realpath($filename)))); |
||||
|
||||
if (isset($json['error'])) { |
||||
if ($estring !== false) $estring = $json['error']; |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
else { |
||||
if ($estring !== false) $estring = "File doesn't exist!"; |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
// force start the specified torrent hashes |
||||
public function torrentForceStart($hash) { |
||||
$this->makeRequest("?action=forcestart".$this->paramImplode("&hash=", $hash), false); |
||||
} |
||||
|
||||
// pause the specified torrent hashes |
||||
public function torrentPause($hash) { |
||||
$this->makeRequest("?action=pause".$this->paramImplode("&hash=", $hash), false); |
||||
} |
||||
|
||||
// recheck the specified torrent hashes |
||||
public function torrentRecheck($hash) { |
||||
$this->makeRequest("?action=recheck".$this->paramImplode("&hash=", $hash), false); |
||||
} |
||||
|
||||
// start the specified torrent hashes |
||||
public function torrentStart($hash) { |
||||
$this->makeRequest("?action=start".$this->paramImplode("&hash=", $hash), false); |
||||
} |
||||
|
||||
// stop the specified torrent hashes |
||||
public function torrentStop($hash) { |
||||
$this->makeRequest("?action=stop".$this->paramImplode("&hash=", $hash), false); |
||||
} |
||||
|
||||
// remove the specified torrent hashes (and data, if $data is set to true) |
||||
public function torrentRemove($hash, $data = false) { |
||||
$this->makeRequest("?action=".($data ? "removedata" : "remove").$this->paramImplode("&hash=", $hash), false); |
||||
} |
||||
} |
@ -0,0 +1,352 @@ |
||||
<?php |
||||
/** |
||||
* 技术讨论及后续更新,请加入QQ群!!!!!!! |
||||
群名称:IYUU自动辅种交流 |
||||
QQ群号:859882209 |
||||
* 手动配置方法,请查看:https://www.iyuu.cn/archives/324/ |
||||
*/ |
||||
return array( |
||||
// 1.爱语飞飞 微信通知配置 |
||||
'iyuu.cn' => 'IYUU', |
||||
// 2.server酱 微信通知配置 |
||||
'sc.ftqq.com' => '', |
||||
// 3.发布员鉴权 |
||||
'secret' => '', |
||||
// 4.全局默认配置 |
||||
'default' => array( |
||||
// 5.【必须配置】浏览器UA,打开http://demo.iyuu.cn 复制过来即可 |
||||
'userAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36', |
||||
// 6.【自动辅种必须配置】全局客户端设置(条目不够可以复制,用不到的请删除) |
||||
'clients' => array( |
||||
// 全局客户端设置 开始 |
||||
array( |
||||
'type' => 'transmission', // 支持:transmission、qBittorrent |
||||
'host' => 'http://127.0.0.1:9091/transmission/rpc', |
||||
'username' => '', |
||||
'password' => '', |
||||
//'move' =>array( |
||||
// 'type' => 2, // 0保持不变,1减,2加, 3直接替换 |
||||
// 'path' =>array( |
||||
// '/sda1' => '/volume1', |
||||
// ), |
||||
//), |
||||
), |
||||
// (条目不够可以复制,用不到的请删除) |
||||
array( |
||||
'type' => 'qBittorrent', // 支持:transmission、qBittorrent |
||||
'host' => 'http://www.baidu.com:8083', |
||||
'username' => '', |
||||
'password' => '', |
||||
), |
||||
// 全局客户端设置 结束 |
||||
), |
||||
'CONNECTTIMEOUT'=> 60, |
||||
'TIMEOUT' => 600, |
||||
), |
||||
/** |
||||
* 以下为各站点的独立配置(互不影响、互不冲突) |
||||
* 自动辅种:需要配置各站的passkey(没有配置passkey的站点会自动跳过) |
||||
*/ |
||||
// m-team 序号:1 |
||||
'm-team' => array( |
||||
// 14.m-team的cookie 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => 'tp=', |
||||
// 15.m-team的passkey 【必须配置】 |
||||
'passkey' => '', |
||||
// 种子Tracker的IP地址选择 可选:ipv4,ipv6 |
||||
'ip_type' => 'ipv4', |
||||
), |
||||
// keepfrds 序号:2 |
||||
'keepfrds' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// ourbits 序号:3 |
||||
'ourbits' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
'id' => 0, // 用户ID |
||||
'is_vip' => 0, // 是否具有VIP或特殊权限?0 普通,1 VIP |
||||
), |
||||
// HDSky 序号:4 |
||||
'hdsky' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// pter 序号:5 |
||||
'pter' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// tjupt 序号:6 |
||||
'tjupt' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hdhome 序号:7 |
||||
'hdhome' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// btschool 序号:8 |
||||
'btschool' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// PTHome 序号:9 |
||||
'pthome' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hddolby 序号:10 |
||||
'hddolby' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// TorrentCCF 序号:11 |
||||
'torrentccf' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// PTMSG 序号:12 |
||||
'ptmsg' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// MoeCat 序号:13 |
||||
'moecat' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
// 种子Tracker的IP地址选择 可选:ipv4,ipv6 |
||||
'ip_type' => 'ipv4', |
||||
), |
||||
// totheglory 序号:14 |
||||
'ttg' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// nanyangpt 序号:15 |
||||
'nanyangpt' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// springsunday.net 序号:16 |
||||
'ssd' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// yingk 序号:17 |
||||
'yingk' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hdcity 序号:18 |
||||
'hdcity' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 cuhash |
||||
'passkey' => '', |
||||
), |
||||
// 52pt.site 序号:19 |
||||
'52pt' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// brobits.cc 序号:20 |
||||
'brobits' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// www.beitai.pt 序号:21 |
||||
'beitai' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// pt.eastgame.org 序号:22 |
||||
'eastgame' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// pt.soulvoice.club 序号:23 |
||||
'soulvoice' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// chdbits 序号:24 |
||||
'chdbits' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// leaguehd 序号:25 |
||||
'leaguehd' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// ptsbao.club 序号:26 |
||||
'ptsbao' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hdchina 序号:27 |
||||
'hdchina' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hdarea 序号:28 |
||||
'hdarea' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hdtime 序号:29 |
||||
'hdtime' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// 1ptba 序号:30 |
||||
'1ptba' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hd4fans 序号:31 |
||||
'hd4fans' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hdbug 序号:32 |
||||
'hdbug' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// opencd 序号:33 |
||||
'opencd' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hdstreet 序号:34 |
||||
'hdstreet' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// joyhd 序号:35 |
||||
'joyhd' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// dmhy 序号:36 |
||||
'dmhy' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// upxin 序号:37 |
||||
'upxin' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// oshen 序号:38 |
||||
'oshen' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// discfan 序号:39 |
||||
'discfan' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// hdzone 序号:40 |
||||
'hdzone' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// cnscg 序号:41 |
||||
'cnscg' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
// nicept 序号:42 |
||||
'nicept' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
), |
||||
|
||||
// 配置文件结束 |
||||
); |
@ -0,0 +1 @@ |
||||
<?php return '20191224.1010'; |
@ -0,0 +1,42 @@ |
||||
<?php |
||||
//---------------------------------- |
||||
// 公共入口文件 |
||||
//---------------------------------- |
||||
// 定义目录 |
||||
defined('ROOT_PATH') or define("ROOT_PATH", dirname(__DIR__)); |
||||
defined('APP_PATH') or define('APP_PATH', __DIR__); |
||||
define('DS', DIRECTORY_SEPARATOR); |
||||
define('TORRENT_PATH', APP_PATH.DS.'torrent'.DS); |
||||
|
||||
// 严格开发模式 |
||||
error_reporting( E_ALL ); |
||||
#ini_set('display_errors', 1); |
||||
|
||||
// 永不超时 |
||||
ini_set('max_execution_time', 0); |
||||
set_time_limit(0); |
||||
// 内存限制,如果外面设置的内存比 /etc/php/php-cli.ini 大,就不要设置了 |
||||
if (intval(ini_get("memory_limit")) < 1024) |
||||
{ |
||||
ini_set('memory_limit', '1024M'); |
||||
} |
||||
|
||||
if( PHP_SAPI != 'cli' ) |
||||
{ |
||||
exit("You must run the CLI environment\n"); |
||||
} |
||||
|
||||
// 设置时区 |
||||
date_default_timezone_set('Asia/Shanghai'); |
||||
|
||||
// 系统配置 |
||||
if( file_exists( APP_PATH."/config/config.php" ) ) |
||||
{ |
||||
// 配置(全局变量) |
||||
$configALL = require_once APP_PATH."/config/config.php"; |
||||
}else{ |
||||
// 示例配置 |
||||
$configALL = require_once APP_PATH . '/config/config.sample.php'; |
||||
} |
||||
|
||||
require_once ROOT_PATH . '/vendor/autoload.php'; |
@ -0,0 +1,10 @@ |
||||
{ |
||||
"require": { |
||||
"owner888/phpspider": "^2.1", |
||||
"curl/curl": "^2.2" |
||||
}, |
||||
"autoload": { |
||||
"classmap":["app/Class"], |
||||
"files": ["app/Class/Function.php"] |
||||
} |
||||
} |
@ -0,0 +1,131 @@ |
||||
{ |
||||
"_readme": [ |
||||
"This file locks the dependencies of your project to a known state", |
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", |
||||
"This file is @generated automatically" |
||||
], |
||||
"content-hash": "32806a4860870f6306b69cf349584387", |
||||
"packages": [ |
||||
{ |
||||
"name": "curl/curl", |
||||
"version": "2.2.0", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/php-mod/curl.git", |
||||
"reference": "d22086dd2eee5ca02e4c29b9a5bdf3645bfdbbff" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/php-mod/curl/zipball/d22086dd2eee5ca02e4c29b9a5bdf3645bfdbbff", |
||||
"reference": "d22086dd2eee5ca02e4c29b9a5bdf3645bfdbbff", |
||||
"shasum": "", |
||||
"mirrors": [ |
||||
{ |
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", |
||||
"preferred": true |
||||
} |
||||
] |
||||
}, |
||||
"require": { |
||||
"ext-curl": "*", |
||||
"php": "^5.6 | ^7.0" |
||||
}, |
||||
"require-dev": { |
||||
"phpunit/phpunit": "^5.7", |
||||
"squizlabs/php_codesniffer": "~2.1" |
||||
}, |
||||
"type": "library", |
||||
"autoload": { |
||||
"psr-0": { |
||||
"Curl": "src/" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Hassan Amouhzi", |
||||
"email": "hassan@anezi.net", |
||||
"homepage": "http://hassan.amouhzi.com" |
||||
}, |
||||
{ |
||||
"name": "php-curl-class", |
||||
"homepage": "https://github.com/php-curl-class" |
||||
}, |
||||
{ |
||||
"name": "user52", |
||||
"homepage": "https://github.com/user52" |
||||
} |
||||
], |
||||
"description": "cURL class for PHP", |
||||
"homepage": "https://github.com/php-mod/curl", |
||||
"keywords": [ |
||||
"curl", |
||||
"dot" |
||||
], |
||||
"time": "2018-12-04T19:47:03+00:00" |
||||
}, |
||||
{ |
||||
"name": "owner888/phpspider", |
||||
"version": "v2.1.6", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/owner888/phpspider.git", |
||||
"reference": "e6021148adec201418c16ba26f39bc013ba5b4d9" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/owner888/phpspider/zipball/e6021148adec201418c16ba26f39bc013ba5b4d9", |
||||
"reference": "e6021148adec201418c16ba26f39bc013ba5b4d9", |
||||
"shasum": "", |
||||
"mirrors": [ |
||||
{ |
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", |
||||
"preferred": true |
||||
} |
||||
] |
||||
}, |
||||
"require": { |
||||
"php": ">=5.5.0" |
||||
}, |
||||
"suggest": { |
||||
"ext-pcntl、ext-redis": "For better performance. " |
||||
}, |
||||
"type": "library", |
||||
"autoload": { |
||||
"psr-4": { |
||||
"phpspider\\": "./" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Seatle Yang", |
||||
"email": "seatle@foxmail.com", |
||||
"homepage": "http://www.phpspider.org", |
||||
"role": "Developer" |
||||
} |
||||
], |
||||
"description": "The PHPSpider Framework.", |
||||
"homepage": "http://www.phpspider.org", |
||||
"keywords": [ |
||||
"framework", |
||||
"phpspider" |
||||
], |
||||
"time": "2018-08-15T08:04:29+00:00" |
||||
} |
||||
], |
||||
"packages-dev": [], |
||||
"aliases": [], |
||||
"minimum-stability": "stable", |
||||
"stability-flags": [], |
||||
"prefer-stable": false, |
||||
"prefer-lowest": false, |
||||
"platform": [], |
||||
"platform-dev": [] |
||||
} |
@ -0,0 +1,612 @@ |
||||
<?php |
||||
/** |
||||
* IYUUAutoReseed自动辅种 |
||||
*/ |
||||
use Curl\Curl; |
||||
require_once __DIR__ . '/app/init.php'; |
||||
iyuuAutoReseed::init(); |
||||
$hashArray = iyuuAutoReseed::get(); |
||||
if ( iyuuAutoReseed::$move != null ) { |
||||
echo "种子移动完毕,请重新编辑配置,再尝试辅种! \n\n"; |
||||
exit; |
||||
} |
||||
iyuuAutoReseed::call($hashArray); |
||||
iyuuAutoReseed::wechatMessage(); |
||||
/** |
||||
* IYUUAutoReseed自动辅种类 |
||||
*/ |
||||
class iyuuAutoReseed |
||||
{ |
||||
/** |
||||
* 版本号 |
||||
* @var string |
||||
*/ |
||||
const VER = '0.1.0'; |
||||
/** |
||||
* RPC连接池 |
||||
* @var array |
||||
*/ |
||||
public static $links = array(); |
||||
/** |
||||
* 客户端配置 |
||||
* @var array |
||||
*/ |
||||
public static $clients = array(); |
||||
/** |
||||
* 不辅种的站点 'ourbits','hdchina' |
||||
* @var array |
||||
*/ |
||||
public static $noReseed = []; |
||||
/** |
||||
* 缓存路径 |
||||
* @var string |
||||
*/ |
||||
public static $cacheDir = TORRENT_PATH.'cache'.DS; |
||||
public static $cacheHash = TORRENT_PATH.'cachehash'.DS; |
||||
/** |
||||
* API接口配置 |
||||
* @var string |
||||
* @var array |
||||
*/ |
||||
public static $apiUrl = 'http://iyuu.cn:2122'; |
||||
public static $endpoints = array( |
||||
'add' => '/api/add', |
||||
'update' => '/api/update', |
||||
'reseed' => '/api/reseed', |
||||
'login' => '/login', |
||||
); |
||||
/** |
||||
* 退出状态码 |
||||
* @var int |
||||
*/ |
||||
public static $ExitCode = 0; |
||||
/** |
||||
* 客户端转移做种 状态码[请把transmission配置为第一个客户端] |
||||
* @var array |
||||
*/ |
||||
public static $move = null; |
||||
/** |
||||
* 微信消息体 |
||||
* @var array |
||||
*/ |
||||
public static $wechatMsg = array( |
||||
'hashCount' => 0, // 提交给服务器的hash总数 |
||||
'sitesCount' => 0, // 可辅种站点总数 |
||||
'reseedCount' => 0, // 返回的总数据 |
||||
'reseedSuccess' => 0, // 成功:辅种成功(会加入缓存,哪怕种子在校验中,下次也会过滤) |
||||
'reseedError' => 0, // 错误:辅种失败(可以重试) |
||||
'reseedRepeat' => 0, // 重复:客户端已做种 |
||||
'reseedSkip' => 0, // 跳过:因未设置passkey,而跳过 |
||||
'reseedPass' => 0, // 忽略:因上次成功添加、存在缓存,而跳过 |
||||
); |
||||
/** |
||||
* 初始化 |
||||
* @return void |
||||
*/ |
||||
public static function init(){ |
||||
global $configALL; |
||||
self::$clients = isset($configALL['default']['clients']) && $configALL['default']['clients'] ? $configALL['default']['clients'] : array(); |
||||
echo "程序正在初始化运行参数... \n"; |
||||
// 递归删除上次历史记录 |
||||
IFile::rmdir(self::$cacheDir, true); |
||||
// 建立目录 |
||||
IFile::mkdir(self::$cacheDir); |
||||
IFile::mkdir(self::$cacheHash); |
||||
// 连接全局客户端 |
||||
self::links(); |
||||
// 合作站点自动注册鉴权 |
||||
Oauth::login(self::$apiUrl . self::$endpoints['login']); |
||||
} |
||||
/** |
||||
* 连接远端RPC服务器 |
||||
* |
||||
* @param string |
||||
* @return bool |
||||
*/ |
||||
public static function links() |
||||
{ |
||||
if(empty(self::$links)){ |
||||
foreach ( self::$clients as $k => $v ){ |
||||
// 跳过未配置的客户端 |
||||
if (empty($v['username']) || empty( $v['password'])) { |
||||
unset(self::$clients[$k]); |
||||
echo "clients_".$k." 用户名或密码未配置,已跳过 \n\n"; |
||||
continue; |
||||
} |
||||
try |
||||
{ |
||||
switch($v['type']){ |
||||
case 'transmission': |
||||
self::$links[$k]['rpc'] = new TransmissionRPC($v['host'], $v['username'], $v['password']); |
||||
$result = self::$links[$k]['rpc']->sstats(); |
||||
print $v['type'].':'.$v['host']." Rpc连接 [{$result->result}] \n"; |
||||
break; |
||||
case 'qBittorrent': |
||||
self::$links[$k]['rpc'] = new qBittorrent($v['host'], $v['username'], $v['password']); |
||||
$result = self::$links[$k]['rpc']->appVersion(); |
||||
print $v['type'].':'.$v['host']." Rpc连接 [{$result}] \n"; |
||||
break; |
||||
default: |
||||
echo '[ERROR] '.$v['type']; |
||||
exit(1); |
||||
break; |
||||
} |
||||
self::$links[$k]['type'] = $v['type']; |
||||
// 检查是否转移种子的做种客户端? |
||||
if ( isset($v['move']) && $v['move'] ) { |
||||
self::$move = array($k,$v['type']); |
||||
} |
||||
} catch (Exception $e) { |
||||
echo '[ERROR] ' . $e->getMessage() . PHP_EOL; |
||||
exit(1); |
||||
} |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
/** |
||||
* 从客户端获取种子的哈希列表 |
||||
* @var array |
||||
*/ |
||||
public static function get(){ |
||||
$hashArray = array(); |
||||
foreach ( self::$clients as $k => $v ){ |
||||
$result = array(); |
||||
$res = $info_hash = array(); |
||||
$json = $sha1 = ''; |
||||
try |
||||
{ |
||||
switch($v['type']){ |
||||
case 'transmission': |
||||
$ids = $fields = array(); |
||||
#$fields = array( "id", "status", "name", "hashString", "downloadDir", "torrentFile" ); |
||||
$fields = array( "id", "status", "hashString", "downloadDir"); |
||||
$result = self::$links[$k]['rpc']->get($ids, $fields); |
||||
if ( empty($result->result) || $result->result != 'success' ){ |
||||
// 获取种子列表 失败 |
||||
echo "获取种子列表失败,原因可能是transmission暂时无响应,请稍后重试! \n"; |
||||
break; |
||||
} |
||||
if( empty($result->arguments) ){ |
||||
echo "未获取到需要辅种的数据,请多多保种,然后重试! \n"; |
||||
break; |
||||
} |
||||
// 对象转数组 |
||||
$res = object_array($result->arguments->torrents); |
||||
// 过滤,只保留正常做种 |
||||
$res = array_filter($res, "filterStatus"); |
||||
// 提取数组:hashString |
||||
$info_hash = array_column($res, 'hashString'); |
||||
// 升序排序 |
||||
sort($info_hash); |
||||
// 微信模板消息 统计 |
||||
self::$wechatMsg['hashCount'] += count($info_hash); |
||||
$json = json_encode($info_hash, JSON_UNESCAPED_UNICODE); |
||||
// 去重 应该从文件读入,防止重复提交 |
||||
$sha1 = sha1( $json ); |
||||
if ( isset($hashArray['sha1']) && (in_array($sha1, $hashArray['sha1']) != false) ) { |
||||
break; |
||||
} |
||||
// 组装返回数据 |
||||
$hashArray['hash']['clients_'.$k] = $json; |
||||
$hashArray['sha1'][] = $sha1; |
||||
// 变换数组:hashString为键 |
||||
self::$links[$k]['hash'] = array_column($res, "downloadDir", 'hashString'); |
||||
#p(self::$links[$k]['hash']);exit; |
||||
break; |
||||
case 'qBittorrent': |
||||
$result = self::$links[$k]['rpc']->torrentList(); |
||||
$res = json_decode($result,true); |
||||
if ( empty($res) ) { |
||||
echo "未获取到需要辅种的数据,请多多保种,然后重试! \n"; |
||||
break; |
||||
} |
||||
#p($res);exit; |
||||
// 过滤,只保留正常做种 |
||||
$res = array_filter($res, "qbfilterStatus"); |
||||
// 提取数组:hashString |
||||
$info_hash = array_column($res, 'hash'); |
||||
// 升序排序 |
||||
sort($info_hash); |
||||
// 微信模板消息 统计 |
||||
self::$wechatMsg['hashCount'] += count($info_hash); |
||||
$json = json_encode($info_hash, JSON_UNESCAPED_UNICODE); |
||||
// 去重 应该从文件读入,防止重复提交 |
||||
$sha1 = sha1( $json ); |
||||
if ( isset($hashArray['sha1']) && (in_array($sha1, $hashArray['sha1']) != false) ) { |
||||
break; |
||||
} |
||||
// 组装返回数据 |
||||
$hashArray['hash']['clients_'.$k] = $json; |
||||
$hashArray['sha1'][] = $sha1; |
||||
// 变换数组:hash为键 |
||||
self::$links[$k]['hash'] = array_column($res, "save_path", 'hash'); |
||||
#p(self::$links[$k]['hash']);exit; |
||||
break; |
||||
default: |
||||
echo '[ERROR] '.$v['type']; |
||||
exit(1); |
||||
break; |
||||
} |
||||
// 是否执行转移种子做种客户端? |
||||
if ( self::$move != null && (empty($v['move'])) ) { |
||||
self::move($res, $v['type']); |
||||
} |
||||
} catch (Exception $e) { |
||||
echo '[ERROR] ' . $e->getMessage() . PHP_EOL; |
||||
exit(1); |
||||
} |
||||
} |
||||
return $hashArray; |
||||
} |
||||
/** |
||||
* @brief 添加下载任务 |
||||
* @param string $torrent 种子元数据 |
||||
* @param string $save_path 保存路径 |
||||
* @return bool |
||||
*/ |
||||
public static function add($rpcKey, $torrent, $save_path = '', $extra_options = array()) |
||||
{ |
||||
try |
||||
{ |
||||
$is_url = false; |
||||
if( (strpos($torrent,'http://')===0) || (strpos($torrent,'https://')===0) || (strpos($torrent,'magnet:?xt=urn:btih:')===0) ){ |
||||
$is_url = true; |
||||
} |
||||
// 下载服务器类型 |
||||
$type = self::$links[$rpcKey]['type']; |
||||
// 判断 |
||||
switch( $type ){ |
||||
case 'transmission': |
||||
$extra_options['paused'] = true; |
||||
if( $is_url ){ |
||||
$result = self::$links[$rpcKey]['rpc']->add( $torrent, $save_path, $extra_options ); // 种子URL添加下载任务 |
||||
} else{ |
||||
$result = self::$links[$rpcKey]['rpc']->add_metainfo( $torrent, $save_path, $extra_options ); // 种子元数据添加下载任务 |
||||
} |
||||
if(isset($result->result) && $result->result == 'success'){ |
||||
$id = $name = ''; |
||||
if( isset($result->arguments->torrent_duplicate) ){ |
||||
$id = $result->arguments->torrent_duplicate->id; |
||||
$name = $result->arguments->torrent_duplicate->name; |
||||
}elseif( isset($result->arguments->torrent_added) ){ |
||||
$id = $result->arguments->torrent_added->id; |
||||
$name = $result->arguments->torrent_added->name; |
||||
} |
||||
print "********RPC添加下载任务成功 [{$result->result}] (id=$id) \n"; |
||||
if( $is_url ){ |
||||
print "种子:".$torrent. "\n"; |
||||
} |
||||
print "名字:".$name."\n\n"; |
||||
return true; |
||||
}else{ |
||||
$errmsg = isset($result->result) ? $result->result : '未知错误,请稍后重试!'; |
||||
print "-----RPC添加种子任务,失败 [{$errmsg}] \n"; |
||||
if( $is_url ){ |
||||
print "种子:".$torrent. "\n"; |
||||
} |
||||
} |
||||
break; |
||||
case 'qBittorrent': |
||||
$extra_options['paused'] = 'true'; |
||||
$extra_options['autoTMM'] = 'false'; //关闭自动种子管理 |
||||
if( $is_url ){ |
||||
$result = self::$links[$rpcKey]['rpc']->add( $torrent, $save_path, $extra_options ); // 种子URL添加下载任务 |
||||
} else{ |
||||
$extra_options['name'] = 'torrents'; |
||||
$extra_options['filename'] = rand(1,4294967200).'.torrent'; |
||||
$result = self::$links[$rpcKey]['rpc']->add_metainfo( $torrent, $save_path, $extra_options ); // 种子元数据添加下载任务 |
||||
} |
||||
if ($result === 'Ok.') { |
||||
print "********RPC添加下载任务成功 [{$result}] \n\n"; |
||||
return true; |
||||
} else { |
||||
print "-----RPC添加种子任务,失败 [{$result}] \n\n"; |
||||
} |
||||
break; |
||||
default: |
||||
echo '[ERROR] '.$type; |
||||
break; |
||||
} |
||||
} catch (Exception $e) { |
||||
echo '[ERROR] ' . $e->getMessage() . PHP_EOL; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 正常做种的种子在各下载器的互相转移 |
||||
*/ |
||||
public static function move($torrent=array(), $type = 'qBittorrent'){ |
||||
switch($type){ |
||||
case 'transmission': |
||||
break; |
||||
case 'qBittorrent': |
||||
foreach ($torrent as $k => $v) { |
||||
// 路径转换 |
||||
#$v['save_path'] = '/volume3' . $v['save_path']; // docker路径转换 |
||||
self::add(self::$move[0], $v['magnet_uri'], $v['save_path'] ); |
||||
} |
||||
break; |
||||
default: |
||||
echo '[ERROR] '.$type; |
||||
break; |
||||
} |
||||
} |
||||
/** |
||||
* @brief 提交种子hash给远端API,用来获取辅种数据 |
||||
* @param array $hashArray 种子hash数组 |
||||
* @return |
||||
*/ |
||||
public static function call($hashArray = array()) |
||||
{ |
||||
global $configALL; |
||||
$resArray = $sites = array(); |
||||
$curl = new Curl(); |
||||
$curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); |
||||
// 签名 |
||||
$hashArray['timestamp'] = time(); |
||||
// 爱语飞飞token |
||||
$hashArray['sign'] = Oauth::getSign(); |
||||
$hashArray['version'] = self::VER; |
||||
// 写日志 |
||||
if (true) { |
||||
// 文件句柄 |
||||
$resource = fopen(self::$cacheDir.'hashString.txt', "wb"); |
||||
// 成功:返回写入字节数,失败返回false |
||||
$worldsnum = fwrite($resource, p($hashArray, false)); |
||||
fclose($resource); |
||||
} |
||||
// 发起请求 |
||||
echo "正在提交辅种信息…… \n"; |
||||
$res = $curl->post(self::$apiUrl . self::$endpoints['reseed'], $hashArray); |
||||
$resArray = json_decode($res->response, true); |
||||
// 写日志 |
||||
if(true){ |
||||
// 文件句柄 |
||||
$resource = fopen(self::$cacheDir.'reseed.txt', "wb"); |
||||
// 成功:返回写入字节数,失败返回false |
||||
$worldsnum = fwrite($resource, p($resArray, false)); |
||||
fclose($resource); |
||||
} |
||||
// 判断返回值 |
||||
if ( isset($resArray['errmsg']) && ($resArray['errmsg'] == 'ok') ) { |
||||
echo "辅种信息提交成功!!! \n\n"; |
||||
}else{ |
||||
$errmsg = isset($resArray['errmsg']) ? $resArray['errmsg'] : '远端服务器无响应,请稍后重试!'; |
||||
echo '-----辅种失败,原因:' .$errmsg. " \n\n"; |
||||
exit(1); |
||||
} |
||||
// 可辅种站点信息列表 |
||||
$sites = $resArray['sites']; |
||||
self::$wechatMsg['sitesCount'] = count($sites); |
||||
#p($sites); |
||||
// 按客户端循环辅种 开始 |
||||
foreach (self::$links as $k => $v) { |
||||
$reseed = $infohash_Dir = array(); |
||||
// info_hash 对应的下载目录 |
||||
$infohash_Dir = self::$links[$k]['hash']; |
||||
if (empty($resArray['clients_'.$k])) { |
||||
echo "clients_".$k."没有查询到可辅种数据 \n\n"; |
||||
continue; |
||||
} |
||||
#p($infohash_Dir); |
||||
// 当前客户端辅种数据 |
||||
$reseed = $resArray['clients_'.$k]; |
||||
foreach ($reseed as $info_hash => $vv) { |
||||
// 当前种子哈希对应的目录 |
||||
$downloadDir = $infohash_Dir[$info_hash]; |
||||
foreach ($vv['torrent'] as $id => $value) { |
||||
// 匹配的辅种数据累加 |
||||
self::$wechatMsg['reseedCount']++; |
||||
// 站点id |
||||
$sitesID = $value['sid']; |
||||
$url = $_url = ''; |
||||
$download_page = $details_url = ''; |
||||
// 页面规则 |
||||
$download_page = str_replace('{}', $value['torrent_id'], $sites[$sitesID]['download_page']); |
||||
$_url = 'https://' .$sites[$sitesID]['base_url']. '/' .$download_page; |
||||
if ( empty($configALL[$sites[$sitesID]['site']]['passkey']) ) { |
||||
echo '-------因当前' .$sites[$sitesID]['site']. '站点未设置passkey,已跳过!!' . "\n\n"; |
||||
self::$wechatMsg['reseedSkip']++; |
||||
continue; |
||||
} |
||||
// 种子URL组合方式区分 |
||||
switch ($sites[$sitesID]['site']) { |
||||
case 'ttg': |
||||
$url = $_url."/". $configALL[$sites[$sitesID]['site']]['passkey']; |
||||
break; |
||||
case 'm-team': |
||||
$ip_type = ''; |
||||
if (isset($configALL[$sites[$sitesID]['site']]['ip_type'])) { |
||||
$ip_type = $configALL[$sites[$sitesID]['site']]['ip_type'] == 'ipv6' ? '&ipv6=1' : ''; |
||||
} |
||||
$url = $_url."&passkey=". $configALL[$sites[$sitesID]['site']]['passkey'] . $ip_type. "&https=1"; |
||||
break; |
||||
case 'moecat': |
||||
$ip_type = ''; |
||||
if (isset($configALL[$sites[$sitesID]['site']]['ip_type'])) { |
||||
$ip_type = $configALL[$sites[$sitesID]['site']]['ip_type'] == 'ipv6' ? '&ipv6=1' : ''; |
||||
} |
||||
$url = $_url."&passkey=". $configALL[$sites[$sitesID]['site']]['passkey'] . $ip_type. "&https=1"; |
||||
break; |
||||
case 'hdchina': |
||||
if ( empty($configALL[$sites[$sitesID]['site']]['cookie']) ) { |
||||
echo '-------因当前' .$sites[$sitesID]['site']. '站点未设置cookie,已跳过!!' . "\n\n"; |
||||
self::$wechatMsg['reseedSkip']++; |
||||
break; |
||||
} |
||||
$cookie = isset($configALL[$sites[$sitesID]['site']]['cookie']) ? $configALL[$sites[$sitesID]['site']]['cookie'] : ''; |
||||
$userAgent = $configALL['default']['userAgent']; |
||||
// 拼接URL |
||||
$details_page = str_replace('{}', $value['torrent_id'], 'details.php?id={}&hit=1'); |
||||
$details_url = 'https://' .$sites[$sitesID]['base_url']. '/' .$details_page; |
||||
$details_html = download($details_url, $cookie, $userAgent); |
||||
print "种子详情页:".$details_url. "\n"; |
||||
// 提取种子下载地址 |
||||
$download_page = str_replace('{}', '', $sites[$sitesID]['download_page']); |
||||
$offset = strpos($details_html, $download_page); |
||||
$urlTemp = substr($details_html, $offset, 50); |
||||
// 种子地址 |
||||
$_url = substr($urlTemp,0,strpos($urlTemp,'">')); |
||||
$_url = 'https://' .$sites[$sitesID]['base_url']. '/' . $_url; |
||||
print "种子下载页:".$_url. "\n"; |
||||
$url = download($_url, $cookie, $userAgent); |
||||
break; |
||||
case 'hdcity': |
||||
if ( empty($configALL[$sites[$sitesID]['site']]['cookie']) ) { |
||||
echo '-------因当前' .$sites[$sitesID]['site']. '站点未设置cookie,已跳过!!' . "\n\n"; |
||||
self::$wechatMsg['reseedSkip']++; |
||||
break; |
||||
} |
||||
$cookie = isset($configALL[$sites[$sitesID]['site']]['cookie']) ? $configALL[$sites[$sitesID]['site']]['cookie'] : ''; |
||||
$userAgent = $configALL['default']['userAgent']; |
||||
print "种子:".$_url. "\n"; |
||||
if ( isset($configALL[$sites[$sitesID]['site']]['cuhash']) ) { |
||||
// 已获取cuhash |
||||
# code... |
||||
}else { |
||||
// 获取cuhash |
||||
$html = download('https://' .$sites[$sitesID]['base_url']. '/pt', $cookie, $userAgent); |
||||
// 提取种子下载地址 |
||||
$offset = strpos($html,'cuhash='); |
||||
$len = strlen('cuhash='); |
||||
$cuhashTemp = substr($html,$offset+$len,40); |
||||
$configALL[$sites[$sitesID]['site']]['cuhash'] = substr($cuhashTemp,0,strpos($cuhashTemp,'"')); |
||||
} |
||||
$url = $_url."&cuhash=". $configALL[$sites[$sitesID]['site']]['cuhash']; |
||||
// 城市下载种子时会302转向 |
||||
$url = download($url, $cookie, $userAgent); |
||||
break; |
||||
default: |
||||
$url = $_url."&passkey=". $configALL[$sites[$sitesID]['site']]['passkey']; |
||||
break; |
||||
} |
||||
/** |
||||
* 检查站点是否可以辅种 |
||||
*/ |
||||
// 判断是否具有VIP或特殊权限? |
||||
$is_vip = isset($configALL[$sites[$sitesID]['site']]['is_vip']) && $configALL[$sites[$sitesID]['site']]['is_vip'] ? 1 : 0; |
||||
if ( (in_array($sites[$sitesID]['site'], self::$noReseed)==false) || $is_vip ) { |
||||
/** |
||||
* 可以辅种 |
||||
*/ |
||||
if ( isset($infohash_Dir[$value['info_hash']]) ) { |
||||
// 与客户端现有种子重复 |
||||
echo '-------与客户端现有种子重复:'.$_url."\n\n"; |
||||
self::$wechatMsg['reseedRepeat']++; |
||||
continue; |
||||
}else{ |
||||
// 判断上次是否成功添加? |
||||
if ( is_file(self::$cacheHash . $value['info_hash'].'.txt') ) { |
||||
echo '-------当前种子上次辅种已成功添加,已跳过!'.$_url."\n\n"; |
||||
self::$wechatMsg['reseedPass']++; |
||||
continue; |
||||
} |
||||
// 把拼接的种子URL,推送给下载器 |
||||
$ret = false; |
||||
// 成功返回:true |
||||
$ret = self::add($k, $url, $downloadDir); |
||||
// 添加成功的种子,以infohash为文件名,写入缓存 |
||||
if ($ret) { |
||||
// 成功的种子 |
||||
// 文件句柄 |
||||
$resource = fopen(self::$cacheHash . $value['info_hash'].'.txt', "wb"); |
||||
// 成功:返回写入字节数,失败返回false |
||||
$worldsnum = fwrite($resource, $url); |
||||
fclose($resource); |
||||
self::$wechatMsg['reseedSuccess']++; |
||||
continue; |
||||
}else{ |
||||
// 失败的种子 |
||||
// 站点类型判断 |
||||
switch ($sites[$sitesID]['site']) { |
||||
case 'hdcity': |
||||
echo '当前' .$sites[$sitesID]['site']. '站点是配置cuhash(不是passkey),添加成功说明配置正确!如果添加任务失败,请查阅常见问题!!' . "\n"; |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
// 失败累加 |
||||
self::$wechatMsg['reseedError']++; |
||||
continue; |
||||
} |
||||
} |
||||
}else{ |
||||
/** |
||||
* 不辅种 |
||||
*/ |
||||
echo '-------已跳过不辅种的站点:'.$_url."\n\n"; |
||||
// 写入日志文件,供用户手动辅种 |
||||
if ( !isset($infohash_Dir[$value['info_hash']]) ) { |
||||
// 站点类型判断 |
||||
switch ($sites[$sitesID]['site']) { |
||||
case 'hdchina': |
||||
$url = $_url; |
||||
break; |
||||
default: |
||||
break; |
||||
} |
||||
// 文件句柄 |
||||
$resource = fopen(self::$cacheDir . $sites[$sitesID]['site'].'.txt', 'a'); |
||||
// 成功:返回写入字节数,失败返回false |
||||
$worldsnum = fwrite($resource, 'clients_'.$k."\n".$downloadDir."\n".$url."\n".$details_url."\n\n"); |
||||
fclose($resource); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
// 按客户端循环辅种 结束 |
||||
} |
||||
/** |
||||
* |
||||
*/ |
||||
public static function wechatMessage(){ |
||||
$br = "\r\n"; |
||||
$text = 'IYUU自动辅种-统计报表'; |
||||
$desp = '总做种:'.self::$wechatMsg['hashCount'] . ' [客户端正在做种的hash总数]' .$br; |
||||
$desp .= '返回数据:'.self::$wechatMsg['reseedCount']. ' [服务器返回的可辅种数据]' .$br; |
||||
$desp .= '支持站点:'.self::$wechatMsg['sitesCount']. ' [当前支持自动辅种的站点数量]' .$br; |
||||
$desp .= '成功:'.self::$wechatMsg['reseedSuccess']. ' [辅种成功,会把hash加入缓存]' .$br; |
||||
$desp .= '失败:'.self::$wechatMsg['reseedError']. ' [下载器下载种子失败或网络超时引起,可以重试]' .$br; |
||||
$desp .= '重复:'.self::$wechatMsg['reseedRepeat']. ' [客户端已做种]' .$br; |
||||
$desp .= '跳过:'.self::$wechatMsg['reseedSkip']. ' [未设置passkey]' .$br; |
||||
$desp .= '忽略:'.self::$wechatMsg['reseedPass']. ' [成功添加存在缓存]' .$br; |
||||
return ff($text, $desp); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* transmission过滤函数,只保留正常做种 |
||||
*/ |
||||
function filterStatus( $v ){ |
||||
return isset($v['status']) && $v['status']===6; |
||||
} |
||||
|
||||
/** |
||||
* qBittorrent过滤函数,只保留正常做种 |
||||
*/ |
||||
function qbfilterStatus( $v ){ |
||||
if( isset($v['state']) && in_array($v['state'], array('uploading','stalledUP','pausedUP','queuedUP','checkingUP','forcedUP')) ){ |
||||
|
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
//PHP stdClass Object转array |
||||
function object_array($array) { |
||||
if(is_object($array)) { |
||||
$array = (array)$array; |
||||
} |
||||
if(is_array($array)) { |
||||
foreach($array as $key=>$value) { |
||||
$array[$key] = object_array($value); |
||||
} |
||||
} |
||||
return $array; |
||||
} |
||||
// 对象转数组 |
||||
function object2array(&$object) { |
||||
return json_decode( json_encode( $object ), true ); |
||||
} |
@ -0,0 +1,101 @@ |
||||
## 功能 |
||||
IYUU自动辅种工具,目前能对国内大部分的PT站点自动辅种,支持下载器集群,支持多盘位,支持多下载目录,支持远程连接等。 |
||||
|
||||
## 原理 |
||||
IYUU自动辅种工具(英文名:IYUUAutoReseed),是一款PHP语言编写的Private Tracker辅种脚本,通过计划任务或常驻内存,按指定频率调用transmission、qBittorrent下载软件的API接口,提取正在做种的info_hash提交到服务器API接口,根据API接口返回的数据拼接种子连接,提交给下载器,自动辅种各个站点。 |
||||
|
||||
## 优势 |
||||
- 全程自动化,无需人工干预; |
||||
- 支持多盘位,多做种目录,多下载器,支持远程连接下载器; |
||||
- 辅种精确度高,精度可配置; |
||||
- 支持微信通知,消息即时达; |
||||
- 自动对合集包,进行拆包辅种(暂未开发) |
||||
|
||||
## 支持的下载器 |
||||
1. transmission |
||||
2. qBittorrent |
||||
|
||||
## 支持自动辅种的站点 |
||||
学校、杜比、家园、天空、朋友、馒头、萌猫、我堡、猫站、铂金家、烧包、北洋、TCCF、南洋、TTG、映客、城市、52pt、brobits、备胎、SSD、CHD、ptmsg、leaguehd、聆音、瓷器、hdarea、eastgame(TLF)、1ptba、hdtime、hd4fans、opencd、hdbug、hdstreet、joyhd、u2、upxin(HDU)、oshen、discfan(GZT)、cnscg圣城。 |
||||
|
||||
## 运行环境 |
||||
所有具备PHP运行环境的所有平台!官方下载的记得开启crul、fileinfo、mbstring,这3个扩展。 |
||||
例如:Linux、Windows、MacOS |
||||
1. Windows下安装php环境:https://www.php.net/downloads |
||||
|
||||
|
||||
## 下载源码 |
||||
- github仓库:https://github.com/ledccn/IYUUAutoReseed |
||||
- 码云仓库:https://gitee.com/ledc/IYUUAutoReseed |
||||
|
||||
## 使用方法 |
||||
详见Wiki: https://gitee.com/ledc/IYUUAutoReseed/wikis |
||||
|
||||
## 需求提交/错误反馈 |
||||
- 点击链接加入群聊【IYUU自动辅种交流】:[https://jq.qq.com/?_wv=1027&k=5JOfOlM][1] |
||||
- QQ群:859882209 |
||||
- issues: https://gitee.com/ledc/IYUUAutoReseed/issues |
||||
|
||||
## 捐助开发者 |
||||
如果觉得我的付出,节约了您的宝贵时间,请随意打赏一杯咖啡!或者一杯水! |
||||
|
||||
如果喜欢,请帮忙在[Github](https://github.com/ledccn/IYUUAutoReseed)或[码云](https://gitee.com/ledc/IYUUAutoReseed)给个Star,也可以对IYUUAutoReseed进行[捐赠](https://gitee.com/ledc/IYUUAutoReseed#%E6%8D%90%E5%8A%A9%E5%BC%80%E5%8F%91%E8%80%85)哦 ^_^。 |
||||
|
||||
**您所有的打赏将用于服务器续期,增加服务的延续性。** |
||||
|
||||
|
||||
![微信打赏.png][2] |
||||
|
||||
|
||||
## 捐赠者列表 |
||||
感谢以下捐赠者,排名不分先后! |
||||
|
||||
|名字 | 金额 | 时间| |
||||
| - | :-: | ---- | |
||||
| 祭 | ¥6元 | 2019年12月10日18:02 | |
||||
| 未署名 | ¥88.88元 | 2019年12月16日20:38 | |
||||
| 当下丶 [阿里云1H2G VPS]2021.9.17 | ¥1300元 | 2019年12月16日16:00 | |
||||
| xzs | ¥20元 | 2019年12月24日11:29 | |
||||
| loveB杉 | ¥20元 | 2019年12月24日20:59 | |
||||
| 风少 | ¥20元 | 2019年12月24日23:30 | |
||||
| 小夏 | ¥1元 | 2019年12月25日11:38 | |
||||
| 优つ伤 | ¥50元 | 2019年12月25日19:21 | |
||||
| Nice | ¥20元 | 2019年12月27日12:54 | |
||||
| 木腕清(天才) | ¥10元 | 2019年12月28日11:26 | |
||||
| @希望功能越来越多 | ¥20元 | 2019年12月28日17:29 | |
||||
| 竹节香附 | ¥20元 | 2019年12月28日18:21 | |
||||
| 李元芳 | ¥6.66 | 2019年12月30日16:19 | |
||||
| Ge(附言:client0修正) | ¥20元 | 2019年12月31日12:02 | |
||||
| 怪叔叔 | ¥20元 | 2019年12月31日15:46 | |
||||
| Shaopeng | ¥10元 | 2020年1月1日18:57 | |
||||
| III(感谢大佬的软件) | ¥10元 | 2020年1月1日22:34 | |
||||
| 子不语 | ¥10元 | 2020年1月3日13:31 | |
||||
| 寒山先生 | ¥100元 | 2020年1月3日20:35 | |
||||
| 阿腾 | ¥20元 | 2020年1月3日22:37 | |
||||
| 手动滑稽 | ¥23.33元 | 2020年1月4日01:38 | |
||||
| 凭樊 | ¥5元 | 2020年1月4日17:58 | |
||||
| Mocar | ¥10元 | 2020年1月4日20:03 | |
||||
| Throne | ¥10元 | 2020年1月4日20:09 | |
||||
| JeSsiE杰西 | ¥200元 | 2020年1月5日09:48 | |
||||
| 人生五十载 | ¥30元 | 2020年1月5日12:29 | |
||||
| C陈奕轰隆隆 | ¥20元 | 2020年1月5日15:55 | |
||||
| 寒山先生 | ¥100元 | 2020年1月6日12:17 | |
||||
| 244574970 | ¥20元 | 2020年1月6日16:18 | |
||||
| Shaopeng | ¥10元 | 2020年1月6日22:01 | |
||||
| 轲 | ¥387元 | 2020年1月7日20:34 | |
||||
| 纸鸢 | ¥2元 | 2020年1月9日11:45 | |
||||
| 寒山先生 | ¥100元 | 2020年1月9日11:51 | |
||||
| 王浩淼 | ¥50元 | 2020年1月9日11:53 | |
||||
| 寒山先生 | ¥100元 | 2020年1月11日11:47 | |
||||
|
||||
补充说明: |
||||
|
||||
1. 此明细不是为了竞价排名,而是以公开、透明的制度说明所捐赠资源的使用情况和去处; |
||||
2. 所捐赠的资源不属于任何个人,而应作为项目或者开发团队的所需开销; |
||||
3. 如果捐赠了却不希望您的名字出现在这里,可以联系我们进行相应处理; |
||||
4. 更新有延时,如未能及时更新,可联系我。 |
||||
|
||||
|
||||
|
||||
[1]: https://jq.qq.com/?_wv=1027&k=5JOfOlM |
||||
[2]: https://www.iyuu.cn/usr/uploads/2019/12/801558607.png |
@ -0,0 +1,7 @@ |
||||
<?php |
||||
|
||||
// autoload.php @generated by Composer |
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php'; |
||||
|
||||
return ComposerAutoloaderInit902220bdd481fe56c25750cdf0255dd6::getLoader(); |
@ -0,0 +1,445 @@ |
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of Composer. |
||||
* |
||||
* (c) Nils Adermann <naderman@naderman.de> |
||||
* Jordi Boggiano <j.boggiano@seld.be> |
||||
* |
||||
* For the full copyright and license information, please view the LICENSE |
||||
* file that was distributed with this source code. |
||||
*/ |
||||
|
||||
namespace Composer\Autoload; |
||||
|
||||
/** |
||||
* ClassLoader implements a PSR-0, PSR-4 and classmap class loader. |
||||
* |
||||
* $loader = new \Composer\Autoload\ClassLoader(); |
||||
* |
||||
* // register classes with namespaces |
||||
* $loader->add('Symfony\Component', __DIR__.'/component'); |
||||
* $loader->add('Symfony', __DIR__.'/framework'); |
||||
* |
||||
* // activate the autoloader |
||||
* $loader->register(); |
||||
* |
||||
* // to enable searching the include path (eg. for PEAR packages) |
||||
* $loader->setUseIncludePath(true); |
||||
* |
||||
* In this example, if you try to use a class in the Symfony\Component |
||||
* namespace or one of its children (Symfony\Component\Console for instance), |
||||
* the autoloader will first look for the class under the component/ |
||||
* directory, and it will then fallback to the framework/ directory if not |
||||
* found before giving up. |
||||
* |
||||
* This class is loosely based on the Symfony UniversalClassLoader. |
||||
* |
||||
* @author Fabien Potencier <fabien@symfony.com> |
||||
* @author Jordi Boggiano <j.boggiano@seld.be> |
||||
* @see http://www.php-fig.org/psr/psr-0/ |
||||
* @see http://www.php-fig.org/psr/psr-4/ |
||||
*/ |
||||
class ClassLoader |
||||
{ |
||||
// PSR-4 |
||||
private $prefixLengthsPsr4 = array(); |
||||
private $prefixDirsPsr4 = array(); |
||||
private $fallbackDirsPsr4 = array(); |
||||
|
||||
// PSR-0 |
||||
private $prefixesPsr0 = array(); |
||||
private $fallbackDirsPsr0 = array(); |
||||
|
||||
private $useIncludePath = false; |
||||
private $classMap = array(); |
||||
private $classMapAuthoritative = false; |
||||
private $missingClasses = array(); |
||||
private $apcuPrefix; |
||||
|
||||
public function getPrefixes() |
||||
{ |
||||
if (!empty($this->prefixesPsr0)) { |
||||
return call_user_func_array('array_merge', $this->prefixesPsr0); |
||||
} |
||||
|
||||
return array(); |
||||
} |
||||
|
||||
public function getPrefixesPsr4() |
||||
{ |
||||
return $this->prefixDirsPsr4; |
||||
} |
||||
|
||||
public function getFallbackDirs() |
||||
{ |
||||
return $this->fallbackDirsPsr0; |
||||
} |
||||
|
||||
public function getFallbackDirsPsr4() |
||||
{ |
||||
return $this->fallbackDirsPsr4; |
||||
} |
||||
|
||||
public function getClassMap() |
||||
{ |
||||
return $this->classMap; |
||||
} |
||||
|
||||
/** |
||||
* @param array $classMap Class to filename map |
||||
*/ |
||||
public function addClassMap(array $classMap) |
||||
{ |
||||
if ($this->classMap) { |
||||
$this->classMap = array_merge($this->classMap, $classMap); |
||||
} else { |
||||
$this->classMap = $classMap; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Registers a set of PSR-0 directories for a given prefix, either |
||||
* appending or prepending to the ones previously set for this prefix. |
||||
* |
||||
* @param string $prefix The prefix |
||||
* @param array|string $paths The PSR-0 root directories |
||||
* @param bool $prepend Whether to prepend the directories |
||||
*/ |
||||
public function add($prefix, $paths, $prepend = false) |
||||
{ |
||||
if (!$prefix) { |
||||
if ($prepend) { |
||||
$this->fallbackDirsPsr0 = array_merge( |
||||
(array) $paths, |
||||
$this->fallbackDirsPsr0 |
||||
); |
||||
} else { |
||||
$this->fallbackDirsPsr0 = array_merge( |
||||
$this->fallbackDirsPsr0, |
||||
(array) $paths |
||||
); |
||||
} |
||||
|
||||
return; |
||||
} |
||||
|
||||
$first = $prefix[0]; |
||||
if (!isset($this->prefixesPsr0[$first][$prefix])) { |
||||
$this->prefixesPsr0[$first][$prefix] = (array) $paths; |
||||
|
||||
return; |
||||
} |
||||
if ($prepend) { |
||||
$this->prefixesPsr0[$first][$prefix] = array_merge( |
||||
(array) $paths, |
||||
$this->prefixesPsr0[$first][$prefix] |
||||
); |
||||
} else { |
||||
$this->prefixesPsr0[$first][$prefix] = array_merge( |
||||
$this->prefixesPsr0[$first][$prefix], |
||||
(array) $paths |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Registers a set of PSR-4 directories for a given namespace, either |
||||
* appending or prepending to the ones previously set for this namespace. |
||||
* |
||||
* @param string $prefix The prefix/namespace, with trailing '\\' |
||||
* @param array|string $paths The PSR-4 base directories |
||||
* @param bool $prepend Whether to prepend the directories |
||||
* |
||||
* @throws \InvalidArgumentException |
||||
*/ |
||||
public function addPsr4($prefix, $paths, $prepend = false) |
||||
{ |
||||
if (!$prefix) { |
||||
// Register directories for the root namespace. |
||||
if ($prepend) { |
||||
$this->fallbackDirsPsr4 = array_merge( |
||||
(array) $paths, |
||||
$this->fallbackDirsPsr4 |
||||
); |
||||
} else { |
||||
$this->fallbackDirsPsr4 = array_merge( |
||||
$this->fallbackDirsPsr4, |
||||
(array) $paths |
||||
); |
||||
} |
||||
} elseif (!isset($this->prefixDirsPsr4[$prefix])) { |
||||
// Register directories for a new namespace. |
||||
$length = strlen($prefix); |
||||
if ('\\' !== $prefix[$length - 1]) { |
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); |
||||
} |
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; |
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths; |
||||
} elseif ($prepend) { |
||||
// Prepend directories for an already registered namespace. |
||||
$this->prefixDirsPsr4[$prefix] = array_merge( |
||||
(array) $paths, |
||||
$this->prefixDirsPsr4[$prefix] |
||||
); |
||||
} else { |
||||
// Append directories for an already registered namespace. |
||||
$this->prefixDirsPsr4[$prefix] = array_merge( |
||||
$this->prefixDirsPsr4[$prefix], |
||||
(array) $paths |
||||
); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Registers a set of PSR-0 directories for a given prefix, |
||||
* replacing any others previously set for this prefix. |
||||
* |
||||
* @param string $prefix The prefix |
||||
* @param array|string $paths The PSR-0 base directories |
||||
*/ |
||||
public function set($prefix, $paths) |
||||
{ |
||||
if (!$prefix) { |
||||
$this->fallbackDirsPsr0 = (array) $paths; |
||||
} else { |
||||
$this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Registers a set of PSR-4 directories for a given namespace, |
||||
* replacing any others previously set for this namespace. |
||||
* |
||||
* @param string $prefix The prefix/namespace, with trailing '\\' |
||||
* @param array|string $paths The PSR-4 base directories |
||||
* |
||||
* @throws \InvalidArgumentException |
||||
*/ |
||||
public function setPsr4($prefix, $paths) |
||||
{ |
||||
if (!$prefix) { |
||||
$this->fallbackDirsPsr4 = (array) $paths; |
||||
} else { |
||||
$length = strlen($prefix); |
||||
if ('\\' !== $prefix[$length - 1]) { |
||||
throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); |
||||
} |
||||
$this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; |
||||
$this->prefixDirsPsr4[$prefix] = (array) $paths; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Turns on searching the include path for class files. |
||||
* |
||||
* @param bool $useIncludePath |
||||
*/ |
||||
public function setUseIncludePath($useIncludePath) |
||||
{ |
||||
$this->useIncludePath = $useIncludePath; |
||||
} |
||||
|
||||
/** |
||||
* Can be used to check if the autoloader uses the include path to check |
||||
* for classes. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function getUseIncludePath() |
||||
{ |
||||
return $this->useIncludePath; |
||||
} |
||||
|
||||
/** |
||||
* Turns off searching the prefix and fallback directories for classes |
||||
* that have not been registered with the class map. |
||||
* |
||||
* @param bool $classMapAuthoritative |
||||
*/ |
||||
public function setClassMapAuthoritative($classMapAuthoritative) |
||||
{ |
||||
$this->classMapAuthoritative = $classMapAuthoritative; |
||||
} |
||||
|
||||
/** |
||||
* Should class lookup fail if not found in the current class map? |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function isClassMapAuthoritative() |
||||
{ |
||||
return $this->classMapAuthoritative; |
||||
} |
||||
|
||||
/** |
||||
* APCu prefix to use to cache found/not-found classes, if the extension is enabled. |
||||
* |
||||
* @param string|null $apcuPrefix |
||||
*/ |
||||
public function setApcuPrefix($apcuPrefix) |
||||
{ |
||||
$this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; |
||||
} |
||||
|
||||
/** |
||||
* The APCu prefix in use, or null if APCu caching is not enabled. |
||||
* |
||||
* @return string|null |
||||
*/ |
||||
public function getApcuPrefix() |
||||
{ |
||||
return $this->apcuPrefix; |
||||
} |
||||
|
||||
/** |
||||
* Registers this instance as an autoloader. |
||||
* |
||||
* @param bool $prepend Whether to prepend the autoloader or not |
||||
*/ |
||||
public function register($prepend = false) |
||||
{ |
||||
spl_autoload_register(array($this, 'loadClass'), true, $prepend); |
||||
} |
||||
|
||||
/** |
||||
* Unregisters this instance as an autoloader. |
||||
*/ |
||||
public function unregister() |
||||
{ |
||||
spl_autoload_unregister(array($this, 'loadClass')); |
||||
} |
||||
|
||||
/** |
||||
* Loads the given class or interface. |
||||
* |
||||
* @param string $class The name of the class |
||||
* @return bool|null True if loaded, null otherwise |
||||
*/ |
||||
public function loadClass($class) |
||||
{ |
||||
if ($file = $this->findFile($class)) { |
||||
includeFile($file); |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Finds the path to the file where the class is defined. |
||||
* |
||||
* @param string $class The name of the class |
||||
* |
||||
* @return string|false The path if found, false otherwise |
||||
*/ |
||||
public function findFile($class) |
||||
{ |
||||
// class map lookup |
||||
if (isset($this->classMap[$class])) { |
||||
return $this->classMap[$class]; |
||||
} |
||||
if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { |
||||
return false; |
||||
} |
||||
if (null !== $this->apcuPrefix) { |
||||
$file = apcu_fetch($this->apcuPrefix.$class, $hit); |
||||
if ($hit) { |
||||
return $file; |
||||
} |
||||
} |
||||
|
||||
$file = $this->findFileWithExtension($class, '.php'); |
||||
|
||||
// Search for Hack files if we are running on HHVM |
||||
if (false === $file && defined('HHVM_VERSION')) { |
||||
$file = $this->findFileWithExtension($class, '.hh'); |
||||
} |
||||
|
||||
if (null !== $this->apcuPrefix) { |
||||
apcu_add($this->apcuPrefix.$class, $file); |
||||
} |
||||
|
||||
if (false === $file) { |
||||
// Remember that this class does not exist. |
||||
$this->missingClasses[$class] = true; |
||||
} |
||||
|
||||
return $file; |
||||
} |
||||
|
||||
private function findFileWithExtension($class, $ext) |
||||
{ |
||||
// PSR-4 lookup |
||||
$logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; |
||||
|
||||
$first = $class[0]; |
||||
if (isset($this->prefixLengthsPsr4[$first])) { |
||||
$subPath = $class; |
||||
while (false !== $lastPos = strrpos($subPath, '\\')) { |
||||
$subPath = substr($subPath, 0, $lastPos); |
||||
$search = $subPath . '\\'; |
||||
if (isset($this->prefixDirsPsr4[$search])) { |
||||
$pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); |
||||
foreach ($this->prefixDirsPsr4[$search] as $dir) { |
||||
if (file_exists($file = $dir . $pathEnd)) { |
||||
return $file; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// PSR-4 fallback dirs |
||||
foreach ($this->fallbackDirsPsr4 as $dir) { |
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { |
||||
return $file; |
||||
} |
||||
} |
||||
|
||||
// PSR-0 lookup |
||||
if (false !== $pos = strrpos($class, '\\')) { |
||||
// namespaced class name |
||||
$logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) |
||||
. strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); |
||||
} else { |
||||
// PEAR-like class name |
||||
$logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; |
||||
} |
||||
|
||||
if (isset($this->prefixesPsr0[$first])) { |
||||
foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { |
||||
if (0 === strpos($class, $prefix)) { |
||||
foreach ($dirs as $dir) { |
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { |
||||
return $file; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// PSR-0 fallback dirs |
||||
foreach ($this->fallbackDirsPsr0 as $dir) { |
||||
if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { |
||||
return $file; |
||||
} |
||||
} |
||||
|
||||
// PSR-0 include paths. |
||||
if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { |
||||
return $file; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Scope isolated include. |
||||
* |
||||
* Prevents access to $this/self from included files. |
||||
*/ |
||||
function includeFile($file) |
||||
{ |
||||
include $file; |
||||
} |
@ -0,0 +1,21 @@ |
||||
|
||||
Copyright (c) Nils Adermann, Jordi Boggiano |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is furnished |
||||
to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
||||
|
@ -0,0 +1,17 @@ |
||||
<?php |
||||
|
||||
// autoload_classmap.php @generated by Composer |
||||
|
||||
$vendorDir = dirname(dirname(__FILE__)); |
||||
$baseDir = dirname($vendorDir); |
||||
|
||||
return array( |
||||
'Bencode' => $baseDir . '/app/Class/Bencode.php', |
||||
'IFile' => $baseDir . '/app/Class/IFile.php', |
||||
'Oauth' => $baseDir . '/app/Class/Oauth.php', |
||||
'Rpc' => $baseDir . '/app/Class/Rpc.php', |
||||
'TransmissionRPC' => $baseDir . '/app/Class/TransmissionRPC.php', |
||||
'TransmissionRPCException' => $baseDir . '/app/Class/TransmissionRPC.php', |
||||
'qBittorrent' => $baseDir . '/app/Class/qBittorrent.php', |
||||
'uTorrent' => $baseDir . '/app/Class/uTorrent.php', |
||||
); |
@ -0,0 +1,10 @@ |
||||
<?php |
||||
|
||||
// autoload_files.php @generated by Composer |
||||
|
||||
$vendorDir = dirname(dirname(__FILE__)); |
||||
$baseDir = dirname($vendorDir); |
||||
|
||||
return array( |
||||
'818e4acb2b433594ec9a26bacb71084d' => $baseDir . '/app/Class/Function.php', |
||||
); |
@ -0,0 +1,10 @@ |
||||
<?php |
||||
|
||||
// autoload_namespaces.php @generated by Composer |
||||
|
||||
$vendorDir = dirname(dirname(__FILE__)); |
||||
$baseDir = dirname($vendorDir); |
||||
|
||||
return array( |
||||
'Curl' => array($vendorDir . '/curl/curl/src'), |
||||
); |
@ -0,0 +1,10 @@ |
||||
<?php |
||||
|
||||
// autoload_psr4.php @generated by Composer |
||||
|
||||
$vendorDir = dirname(dirname(__FILE__)); |
||||
$baseDir = dirname($vendorDir); |
||||
|
||||
return array( |
||||
'phpspider\\' => array($vendorDir . '/owner888/phpspider'), |
||||
); |
@ -0,0 +1,70 @@ |
||||
<?php |
||||
|
||||
// autoload_real.php @generated by Composer |
||||
|
||||
class ComposerAutoloaderInit902220bdd481fe56c25750cdf0255dd6 |
||||
{ |
||||
private static $loader; |
||||
|
||||
public static function loadClassLoader($class) |
||||
{ |
||||
if ('Composer\Autoload\ClassLoader' === $class) { |
||||
require __DIR__ . '/ClassLoader.php'; |
||||
} |
||||
} |
||||
|
||||
public static function getLoader() |
||||
{ |
||||
if (null !== self::$loader) { |
||||
return self::$loader; |
||||
} |
||||
|
||||
spl_autoload_register(array('ComposerAutoloaderInit902220bdd481fe56c25750cdf0255dd6', 'loadClassLoader'), true, true); |
||||
self::$loader = $loader = new \Composer\Autoload\ClassLoader(); |
||||
spl_autoload_unregister(array('ComposerAutoloaderInit902220bdd481fe56c25750cdf0255dd6', 'loadClassLoader')); |
||||
|
||||
$useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); |
||||
if ($useStaticLoader) { |
||||
require_once __DIR__ . '/autoload_static.php'; |
||||
|
||||
call_user_func(\Composer\Autoload\ComposerStaticInit902220bdd481fe56c25750cdf0255dd6::getInitializer($loader)); |
||||
} else { |
||||
$map = require __DIR__ . '/autoload_namespaces.php'; |
||||
foreach ($map as $namespace => $path) { |
||||
$loader->set($namespace, $path); |
||||
} |
||||
|
||||
$map = require __DIR__ . '/autoload_psr4.php'; |
||||
foreach ($map as $namespace => $path) { |
||||
$loader->setPsr4($namespace, $path); |
||||
} |
||||
|
||||
$classMap = require __DIR__ . '/autoload_classmap.php'; |
||||
if ($classMap) { |
||||
$loader->addClassMap($classMap); |
||||
} |
||||
} |
||||
|
||||
$loader->register(true); |
||||
|
||||
if ($useStaticLoader) { |
||||
$includeFiles = Composer\Autoload\ComposerStaticInit902220bdd481fe56c25750cdf0255dd6::$files; |
||||
} else { |
||||
$includeFiles = require __DIR__ . '/autoload_files.php'; |
||||
} |
||||
foreach ($includeFiles as $fileIdentifier => $file) { |
||||
composerRequire902220bdd481fe56c25750cdf0255dd6($fileIdentifier, $file); |
||||
} |
||||
|
||||
return $loader; |
||||
} |
||||
} |
||||
|
||||
function composerRequire902220bdd481fe56c25750cdf0255dd6($fileIdentifier, $file) |
||||
{ |
||||
if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { |
||||
require $file; |
||||
|
||||
$GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
<?php |
||||
|
||||
// autoload_static.php @generated by Composer |
||||
|
||||
namespace Composer\Autoload; |
||||
|
||||
class ComposerStaticInit902220bdd481fe56c25750cdf0255dd6 |
||||
{ |
||||
public static $files = array ( |
||||
'818e4acb2b433594ec9a26bacb71084d' => __DIR__ . '/../..' . '/app/Class/Function.php', |
||||
); |
||||
|
||||
public static $prefixLengthsPsr4 = array ( |
||||
'p' => |
||||
array ( |
||||
'phpspider\\' => 10, |
||||
), |
||||
); |
||||
|
||||
public static $prefixDirsPsr4 = array ( |
||||
'phpspider\\' => |
||||
array ( |
||||
0 => __DIR__ . '/..' . '/owner888/phpspider', |
||||
), |
||||
); |
||||
|
||||
public static $prefixesPsr0 = array ( |
||||
'C' => |
||||
array ( |
||||
'Curl' => |
||||
array ( |
||||
0 => __DIR__ . '/..' . '/curl/curl/src', |
||||
), |
||||
), |
||||
); |
||||
|
||||
public static $classMap = array ( |
||||
'Bencode' => __DIR__ . '/../..' . '/app/Class/Bencode.php', |
||||
'IFile' => __DIR__ . '/../..' . '/app/Class/IFile.php', |
||||
'Oauth' => __DIR__ . '/../..' . '/app/Class/Oauth.php', |
||||
'Rpc' => __DIR__ . '/../..' . '/app/Class/Rpc.php', |
||||
'TransmissionRPC' => __DIR__ . '/../..' . '/app/Class/TransmissionRPC.php', |
||||
'TransmissionRPCException' => __DIR__ . '/../..' . '/app/Class/TransmissionRPC.php', |
||||
'qBittorrent' => __DIR__ . '/../..' . '/app/Class/qBittorrent.php', |
||||
'uTorrent' => __DIR__ . '/../..' . '/app/Class/uTorrent.php', |
||||
); |
||||
|
||||
public static function getInitializer(ClassLoader $loader) |
||||
{ |
||||
return \Closure::bind(function () use ($loader) { |
||||
$loader->prefixLengthsPsr4 = ComposerStaticInit902220bdd481fe56c25750cdf0255dd6::$prefixLengthsPsr4; |
||||
$loader->prefixDirsPsr4 = ComposerStaticInit902220bdd481fe56c25750cdf0255dd6::$prefixDirsPsr4; |
||||
$loader->prefixesPsr0 = ComposerStaticInit902220bdd481fe56c25750cdf0255dd6::$prefixesPsr0; |
||||
$loader->classMap = ComposerStaticInit902220bdd481fe56c25750cdf0255dd6::$classMap; |
||||
|
||||
}, null, ClassLoader::class); |
||||
} |
||||
} |
@ -0,0 +1,119 @@ |
||||
[ |
||||
{ |
||||
"name": "curl/curl", |
||||
"version": "2.2.0", |
||||
"version_normalized": "2.2.0.0", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/php-mod/curl.git", |
||||
"reference": "d22086dd2eee5ca02e4c29b9a5bdf3645bfdbbff" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/php-mod/curl/zipball/d22086dd2eee5ca02e4c29b9a5bdf3645bfdbbff", |
||||
"reference": "d22086dd2eee5ca02e4c29b9a5bdf3645bfdbbff", |
||||
"shasum": "", |
||||
"mirrors": [ |
||||
{ |
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", |
||||
"preferred": true |
||||
} |
||||
] |
||||
}, |
||||
"require": { |
||||
"ext-curl": "*", |
||||
"php": "^5.6 | ^7.0" |
||||
}, |
||||
"require-dev": { |
||||
"phpunit/phpunit": "^5.7", |
||||
"squizlabs/php_codesniffer": "~2.1" |
||||
}, |
||||
"time": "2018-12-04T19:47:03+00:00", |
||||
"type": "library", |
||||
"installation-source": "dist", |
||||
"autoload": { |
||||
"psr-0": { |
||||
"Curl": "src/" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Hassan Amouhzi", |
||||
"email": "hassan@anezi.net", |
||||
"homepage": "http://hassan.amouhzi.com" |
||||
}, |
||||
{ |
||||
"name": "php-curl-class", |
||||
"homepage": "https://github.com/php-curl-class" |
||||
}, |
||||
{ |
||||
"name": "user52", |
||||
"homepage": "https://github.com/user52" |
||||
} |
||||
], |
||||
"description": "cURL class for PHP", |
||||
"homepage": "https://github.com/php-mod/curl", |
||||
"keywords": [ |
||||
"curl", |
||||
"dot" |
||||
] |
||||
}, |
||||
{ |
||||
"name": "owner888/phpspider", |
||||
"version": "v2.1.6", |
||||
"version_normalized": "2.1.6.0", |
||||
"source": { |
||||
"type": "git", |
||||
"url": "https://github.com/owner888/phpspider.git", |
||||
"reference": "e6021148adec201418c16ba26f39bc013ba5b4d9" |
||||
}, |
||||
"dist": { |
||||
"type": "zip", |
||||
"url": "https://api.github.com/repos/owner888/phpspider/zipball/e6021148adec201418c16ba26f39bc013ba5b4d9", |
||||
"reference": "e6021148adec201418c16ba26f39bc013ba5b4d9", |
||||
"shasum": "", |
||||
"mirrors": [ |
||||
{ |
||||
"url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", |
||||
"preferred": true |
||||
} |
||||
] |
||||
}, |
||||
"require": { |
||||
"php": ">=5.5.0" |
||||
}, |
||||
"suggest": { |
||||
"ext-pcntl、ext-redis": "For better performance. " |
||||
}, |
||||
"time": "2018-08-15T08:04:29+00:00", |
||||
"type": "library", |
||||
"installation-source": "dist", |
||||
"autoload": { |
||||
"psr-4": { |
||||
"phpspider\\": "./" |
||||
} |
||||
}, |
||||
"notification-url": "https://packagist.org/downloads/", |
||||
"license": [ |
||||
"MIT" |
||||
], |
||||
"authors": [ |
||||
{ |
||||
"name": "Seatle Yang", |
||||
"email": "seatle@foxmail.com", |
||||
"homepage": "http://www.phpspider.org", |
||||
"role": "Developer" |
||||
} |
||||
], |
||||
"description": "The PHPSpider Framework.", |
||||
"homepage": "http://www.phpspider.org", |
||||
"keywords": [ |
||||
"framework", |
||||
"phpspider" |
||||
] |
||||
} |
||||
] |
@ -0,0 +1,11 @@ |
||||
vendor/* |
||||
*.orig |
||||
.buildpath |
||||
.project |
||||
.settings/* |
||||
.idea/* |
||||
composer.lock |
||||
*~ |
||||
tests/phpunit_report/* |
||||
/.settings/ |
||||
/.php_cs.cache |
@ -0,0 +1,113 @@ |
||||
stages: |
||||
- build |
||||
- test |
||||
|
||||
build-test-server: |
||||
image: docker:latest |
||||
stage: build |
||||
services: |
||||
- docker:dind |
||||
script: |
||||
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY |
||||
- docker build --pull -t "$CI_REGISTRY_IMAGE:server-test" tests/server |
||||
- docker push "$CI_REGISTRY_IMAGE:server-test" |
||||
only: |
||||
changes: |
||||
- tests/server |
||||
|
||||
tests-php5.6: |
||||
image: alpine:3.7 |
||||
stage: test |
||||
services: |
||||
- name: "$CI_REGISTRY_IMAGE:server-test" |
||||
alias: server_test |
||||
script: |
||||
- apk add --no-cache php5-cli php5-curl php5-gd php5-phar php5-json php5-openssl php5-dom php5-xml php5-zlib |
||||
- ln -s /usr/bin/php5 /usr/bin/php |
||||
- php --version |
||||
- if [ ! -f composer.phar ]; then DOWLOAD_COMPOSER=1 ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "if (hash_file('sha384', 'composer-setup.php') === '93b54496392c062774670ac18b134c3b3a95e5a5e5c8f1a9f115f203b75bf9a129d5daa8ba6a13e2cc8a1da0806388a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php composer-setup.php ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "unlink('composer-setup.php');" ; fi; |
||||
- php composer.phar install |
||||
- vendor/bin/phpcs --warning-severity=0 --standard=PSR2 src |
||||
- vendor/bin/phpunit |
||||
cache: |
||||
key: php5.6 |
||||
paths: |
||||
- composer.phar |
||||
- vendor |
||||
|
||||
tests-php7.0: |
||||
image: alpine:3.5 |
||||
stage: test |
||||
services: |
||||
- name: "$CI_REGISTRY_IMAGE:server-test" |
||||
alias: server_test |
||||
script: |
||||
- apk add --no-cache php7 php7-curl php7-gd php7-phar php7-json php7-openssl php7-dom php7-mbstring |
||||
- ln -s /usr/bin/php7 /usr/bin/php |
||||
- php --version |
||||
- if [ ! -f composer.phar ]; then DOWLOAD_COMPOSER=1 ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "if (hash_file('sha384', 'composer-setup.php') === '93b54496392c062774670ac18b134c3b3a95e5a5e5c8f1a9f115f203b75bf9a129d5daa8ba6a13e2cc8a1da0806388a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php composer-setup.php ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "unlink('composer-setup.php');" ; fi; |
||||
- php composer.phar install |
||||
- vendor/bin/phpcs --warning-severity=0 --standard=PSR2 src |
||||
- nohup php -S localhost:8000 -t tests/server/php-curl-test > phpd.log 2>&1 & |
||||
- vendor/bin/phpunit |
||||
cache: |
||||
key: php7.0 |
||||
paths: |
||||
- composer.phar |
||||
- vendor |
||||
|
||||
tests-php7.1: |
||||
image: alpine:3.7 |
||||
stage: test |
||||
services: |
||||
- name: "$CI_REGISTRY_IMAGE:server-test" |
||||
alias: server_test |
||||
script: |
||||
- apk add --no-cache php7-cli php7-curl php7-gd php7-phar php7-json php7-openssl php7-dom php7-simplexml php7-tokenizer php7-mbstring php7-xml |
||||
- php --version |
||||
- if [ ! -f composer.phar ]; then DOWLOAD_COMPOSER=1 ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "if (hash_file('sha384', 'composer-setup.php') === '93b54496392c062774670ac18b134c3b3a95e5a5e5c8f1a9f115f203b75bf9a129d5daa8ba6a13e2cc8a1da0806388a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php composer-setup.php ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "unlink('composer-setup.php');" ; fi; |
||||
- php composer.phar install |
||||
- vendor/bin/phpcs --warning-severity=0 --standard=PSR2 src |
||||
- nohup php -S localhost:8000 -t tests/server/php-curl-test > phpd.log 2>&1 & |
||||
- vendor/bin/phpunit |
||||
cache: |
||||
key: php7.1 |
||||
paths: |
||||
- composer.phar |
||||
- vendor |
||||
|
||||
tests-php7.2: |
||||
image: alpine:3.8 |
||||
stage: test |
||||
services: |
||||
- name: "$CI_REGISTRY_IMAGE:server-test" |
||||
alias: server_test |
||||
script: |
||||
- apk add --no-cache php7-cli php7-curl php7-gd php7-phar php7-json php7-openssl php7-dom php7-simplexml php7-tokenizer php7-mbstring php7-xml |
||||
- php --version |
||||
- if [ ! -f composer.phar ]; then DOWLOAD_COMPOSER=1 ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "if (hash_file('sha384', 'composer-setup.php') === '93b54496392c062774670ac18b134c3b3a95e5a5e5c8f1a9f115f203b75bf9a129d5daa8ba6a13e2cc8a1da0806388a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php composer-setup.php ; fi; |
||||
- if [ -n "$DOWLOAD_COMPOSER" ] ; then php -r "unlink('composer-setup.php');" ; fi; |
||||
- php composer.phar install |
||||
- vendor/bin/phpcs --warning-severity=0 --standard=PSR2 src |
||||
- nohup php -S localhost:8000 -t tests/server/php-curl-test > phpd.log 2>&1 & |
||||
- vendor/bin/phpunit |
||||
cache: |
||||
key: php7.2 |
||||
paths: |
||||
- composer.phar |
||||
- vendor |
@ -0,0 +1,20 @@ |
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2013 php-mod |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
||||
this software and associated documentation files (the "Software"), to deal in |
||||
the Software without restriction, including without limitation the rights to |
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
||||
the Software, and to permit persons to whom the Software is furnished to do so, |
||||
subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,125 @@ |
||||
# PHP Curl Class |
||||
|
||||
This library provides an object-oriented wrapper of the PHP cURL extension. |
||||
|
||||
If you have questions or problems with installation or usage [create an Issue](https://github.com/php-mod/curl/issues). |
||||
|
||||
## Installation |
||||
|
||||
In order to install this library via composer run the following command in the console: |
||||
|
||||
```sh |
||||
composer require curl/curl |
||||
``` |
||||
|
||||
or add the package manually to your composer.json file in the require section: |
||||
|
||||
```json |
||||
"curl/curl": "^2.0" |
||||
``` |
||||
|
||||
## Usage examples |
||||
|
||||
```php |
||||
$curl = new Curl\Curl(); |
||||
$curl->get('http://www.example.com/'); |
||||
``` |
||||
|
||||
```php |
||||
$curl = new Curl\Curl(); |
||||
$curl->get('http://www.example.com/search', array( |
||||
'q' => 'keyword', |
||||
)); |
||||
``` |
||||
|
||||
```php |
||||
$curl = new Curl\Curl(); |
||||
$curl->post('http://www.example.com/login/', array( |
||||
'username' => 'myusername', |
||||
'password' => 'mypassword', |
||||
)); |
||||
``` |
||||
|
||||
```php |
||||
$curl = new Curl\Curl(); |
||||
$curl->setBasicAuthentication('username', 'password'); |
||||
$curl->setUserAgent(''); |
||||
$curl->setReferrer(''); |
||||
$curl->setHeader('X-Requested-With', 'XMLHttpRequest'); |
||||
$curl->setCookie('key', 'value'); |
||||
$curl->get('http://www.example.com/'); |
||||
|
||||
if ($curl->error) { |
||||
echo $curl->error_code; |
||||
} |
||||
else { |
||||
echo $curl->response; |
||||
} |
||||
|
||||
var_dump($curl->request_headers); |
||||
var_dump($curl->response_headers); |
||||
``` |
||||
|
||||
```php |
||||
$curl = new Curl\Curl(); |
||||
$curl->setOpt(CURLOPT_RETURNTRANSFER, TRUE); |
||||
$curl->setOpt(CURLOPT_SSL_VERIFYPEER, FALSE); |
||||
$curl->get('https://encrypted.example.com/'); |
||||
``` |
||||
|
||||
```php |
||||
$curl = new Curl\Curl(); |
||||
$curl->put('http://api.example.com/user/', array( |
||||
'first_name' => 'Zach', |
||||
'last_name' => 'Borboa', |
||||
)); |
||||
``` |
||||
|
||||
```php |
||||
$curl = new Curl\Curl(); |
||||
$curl->patch('http://api.example.com/profile/', array( |
||||
'image' => '@path/to/file.jpg', |
||||
)); |
||||
``` |
||||
|
||||
```php |
||||
$curl = new Curl\Curl(); |
||||
$curl->delete('http://api.example.com/user/', array( |
||||
'id' => '1234', |
||||
)); |
||||
``` |
||||
|
||||
```php |
||||
$curl->close(); |
||||
``` |
||||
|
||||
```php |
||||
// Example access to curl object. |
||||
curl_set_opt($curl->curl, CURLOPT_USERAGENT, 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1'); |
||||
curl_close($curl->curl); |
||||
``` |
||||
|
||||
```php |
||||
// Example of downloading a file or any other content |
||||
$curl = new Curl\Curl(); |
||||
// open the file where the request response should be written |
||||
$file_handle = fopen($target_file, 'w+'); |
||||
// pass it to the curl resource |
||||
$curl->setOpt(CURLOPT_FILE, $file_handle); |
||||
// do any type of request |
||||
$curl->get('https://github.com'); |
||||
// disable writing to file |
||||
$curl->setOpt(CURLOPT_FILE, null); |
||||
// close the file for writing |
||||
fclose($file_handle); |
||||
``` |
||||
|
||||
|
||||
## Testing |
||||
|
||||
In order to test the library: |
||||
|
||||
1. Create a fork |
||||
2. Clone the fork to your machine |
||||
3. Install the depencies `composer install` |
||||
4. Run the unit tests `./vendor/bin/phpunit tests` |
@ -0,0 +1,36 @@ |
||||
{ |
||||
"name": "curl/curl", |
||||
"description": "cURL class for PHP", |
||||
"keywords": ["dot", "curl"], |
||||
"homepage": "https://github.com/php-mod/curl", |
||||
"type": "library", |
||||
"license": "MIT", |
||||
"authors": [ |
||||
{ |
||||
"name": "php-curl-class", |
||||
"homepage": "https://github.com/php-curl-class" |
||||
}, |
||||
{ |
||||
"name": "Hassan Amouhzi", |
||||
"email": "hassan@anezi.net", |
||||
"homepage": "http://hassan.amouhzi.com" |
||||
}, |
||||
{ |
||||
"name": "user52", |
||||
"homepage": "https://github.com/user52" |
||||
} |
||||
], |
||||
"require": { |
||||
"php": "^5.6 | ^7.0", |
||||
"ext-curl": "*" |
||||
}, |
||||
"require-dev": { |
||||
"phpunit/phpunit": "^5.7", |
||||
"squizlabs/php_codesniffer": "~2.1" |
||||
}, |
||||
"autoload": { |
||||
"psr-0": { |
||||
"Curl": "src/" |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<phpunit backupGlobals="false" |
||||
backupStaticAttributes="false" |
||||
colors="true" |
||||
convertErrorsToExceptions="true" |
||||
convertNoticesToExceptions="true" |
||||
convertWarningsToExceptions="true" |
||||
processIsolation="false" |
||||
stopOnFailure="false" |
||||
syntaxCheck="true" |
||||
verbose="false" |
||||
bootstrap="vendor/autoload.php" |
||||
> |
||||
<php> |
||||
<ini name="display_errors" value="on"/> |
||||
</php> |
||||
|
||||
<testsuites> |
||||
<testsuite name="PHP MP4Box Tests Suite"> |
||||
<directory>tests</directory> |
||||
</testsuite> |
||||
</testsuites> |
||||
|
||||
</phpunit> |
@ -0,0 +1,719 @@ |
||||
<?php |
||||
|
||||
namespace Curl; |
||||
|
||||
/** |
||||
* An object-oriented wrapper of the PHP cURL extension. |
||||
* |
||||
* This library requires to have the php cURL extensions installed: |
||||
* https://php.net/manual/curl.setup.php |
||||
* |
||||
* Example of making a get request with parameters: |
||||
* |
||||
* ```php |
||||
* $curl = new Curl\Curl(); |
||||
* $curl->get('http://www.example.com/search', array( |
||||
* 'q' => 'keyword', |
||||
* )); |
||||
* ``` |
||||
* |
||||
* Example post request with post data: |
||||
* |
||||
* ```php |
||||
* $curl = new Curl\Curl(); |
||||
* $curl->post('http://www.example.com/login/', array( |
||||
* 'username' => 'myusername', |
||||
* 'password' => 'mypassword', |
||||
* )); |
||||
* ``` |
||||
* |
||||
* @see https://php.net/manual/curl.setup.php |
||||
*/ |
||||
class Curl |
||||
{ |
||||
// The HTTP authentication method(s) to use. |
||||
|
||||
/** |
||||
* @var string Type AUTH_BASIC |
||||
*/ |
||||
const AUTH_BASIC = CURLAUTH_BASIC; |
||||
|
||||
/** |
||||
* @var string Type AUTH_DIGEST |
||||
*/ |
||||
const AUTH_DIGEST = CURLAUTH_DIGEST; |
||||
|
||||
/** |
||||
* @var string Type AUTH_GSSNEGOTIATE |
||||
*/ |
||||
const AUTH_GSSNEGOTIATE = CURLAUTH_GSSNEGOTIATE; |
||||
|
||||
/** |
||||
* @var string Type AUTH_NTLM |
||||
*/ |
||||
const AUTH_NTLM = CURLAUTH_NTLM; |
||||
|
||||
/** |
||||
* @var string Type AUTH_ANY |
||||
*/ |
||||
const AUTH_ANY = CURLAUTH_ANY; |
||||
|
||||
/** |
||||
* @var string Type AUTH_ANYSAFE |
||||
*/ |
||||
const AUTH_ANYSAFE = CURLAUTH_ANYSAFE; |
||||
|
||||
/** |
||||
* @var string The user agent name which is set when making a request |
||||
*/ |
||||
const USER_AGENT = 'PHP Curl/1.9 (+https://github.com/php-mod/curl)'; |
||||
|
||||
private $_cookies = array(); |
||||
|
||||
private $_headers = array(); |
||||
|
||||
/** |
||||
* @var resource Contains the curl resource created by `curl_init()` function |
||||
*/ |
||||
public $curl; |
||||
|
||||
/** |
||||
* @var bool Whether an error occured or not |
||||
*/ |
||||
public $error = false; |
||||
|
||||
/** |
||||
* @var int Contains the error code of the curren request, 0 means no error happend |
||||
*/ |
||||
public $error_code = 0; |
||||
|
||||
/** |
||||
* @var string If the curl request failed, the error message is contained |
||||
*/ |
||||
public $error_message = null; |
||||
|
||||
/** |
||||
* @var bool Whether an error occured or not |
||||
*/ |
||||
public $curl_error = false; |
||||
|
||||
/** |
||||
* @var int Contains the error code of the curren request, 0 means no error happend. |
||||
* @see https://curl.haxx.se/libcurl/c/libcurl-errors.html |
||||
*/ |
||||
public $curl_error_code = 0; |
||||
|
||||
/** |
||||
* @var string If the curl request failed, the error message is contained |
||||
*/ |
||||
public $curl_error_message = null; |
||||
|
||||
/** |
||||
* @var bool Whether an error occured or not |
||||
*/ |
||||
public $http_error = false; |
||||
|
||||
/** |
||||
* @var int Contains the status code of the current processed request. |
||||
*/ |
||||
public $http_status_code = 0; |
||||
|
||||
/** |
||||
* @var string If the curl request failed, the error message is contained |
||||
*/ |
||||
public $http_error_message = null; |
||||
|
||||
/** |
||||
* @var string|array TBD (ensure type) Contains the request header informations |
||||
*/ |
||||
public $request_headers = null; |
||||
|
||||
/** |
||||
* @var string|array TBD (ensure type) Contains the response header informations |
||||
*/ |
||||
public $response_headers = array(); |
||||
|
||||
/** |
||||
* @var string Contains the response from the curl request |
||||
*/ |
||||
public $response = null; |
||||
|
||||
/** |
||||
* @var bool Whether the current section of response headers is after 'HTTP/1.1 100 Continue' |
||||
*/ |
||||
protected $response_header_continue = false; |
||||
|
||||
/** |
||||
* Constructor ensures the available curl extension is loaded. |
||||
* |
||||
* @throws \ErrorException |
||||
*/ |
||||
public function __construct() |
||||
{ |
||||
if (!extension_loaded('curl')) { |
||||
throw new \ErrorException('The cURL extensions is not loaded, make sure you have installed the cURL extension: https://php.net/manual/curl.setup.php'); |
||||
} |
||||
|
||||
$this->init(); |
||||
} |
||||
|
||||
// private methods |
||||
|
||||
/** |
||||
* Initializer for the curl resource. |
||||
* |
||||
* Is called by the __construct() of the class or when the curl request is reseted. |
||||
* @return self |
||||
*/ |
||||
private function init() |
||||
{ |
||||
$this->curl = curl_init(); |
||||
$this->setUserAgent(self::USER_AGENT); |
||||
$this->setOpt(CURLINFO_HEADER_OUT, true); |
||||
$this->setOpt(CURLOPT_HEADER, false); |
||||
$this->setOpt(CURLOPT_RETURNTRANSFER, true); |
||||
$this->setOpt(CURLOPT_HEADERFUNCTION, array($this, 'addResponseHeaderLine')); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Handle writing the response headers |
||||
* |
||||
* @param resource $curl The current curl resource |
||||
* @param string $header_line A line from the list of response headers |
||||
* |
||||
* @return int Returns the length of the $header_line |
||||
*/ |
||||
public function addResponseHeaderLine($curl, $header_line) |
||||
{ |
||||
$trimmed_header = trim($header_line, "\r\n"); |
||||
|
||||
if ($trimmed_header === "") { |
||||
$this->response_header_continue = false; |
||||
} elseif (strtolower($trimmed_header) === 'http/1.1 100 continue') { |
||||
$this->response_header_continue = true; |
||||
} elseif (!$this->response_header_continue) { |
||||
$this->response_headers[] = $trimmed_header; |
||||
} |
||||
|
||||
return strlen($header_line); |
||||
} |
||||
|
||||
// protected methods |
||||
|
||||
/** |
||||
* Execute the curl request based on the respectiv settings. |
||||
* |
||||
* @return int Returns the error code for the current curl request |
||||
*/ |
||||
protected function exec() |
||||
{ |
||||
$this->response_headers = array(); |
||||
$this->response = curl_exec($this->curl); |
||||
$this->curl_error_code = curl_errno($this->curl); |
||||
$this->curl_error_message = curl_error($this->curl); |
||||
$this->curl_error = !($this->curl_error_code === 0); |
||||
$this->http_status_code = curl_getinfo($this->curl, CURLINFO_HTTP_CODE); |
||||
$this->http_error = in_array(floor($this->http_status_code / 100), array(4, 5)); |
||||
$this->error = $this->curl_error || $this->http_error; |
||||
$this->error_code = $this->error ? ($this->curl_error ? $this->curl_error_code : $this->http_status_code) : 0; |
||||
$this->request_headers = preg_split('/\r\n/', curl_getinfo($this->curl, CURLINFO_HEADER_OUT), null, PREG_SPLIT_NO_EMPTY); |
||||
$this->http_error_message = $this->error ? (isset($this->response_headers['0']) ? $this->response_headers['0'] : '') : ''; |
||||
$this->error_message = $this->curl_error ? $this->curl_error_message : $this->http_error_message; |
||||
|
||||
return $this->error_code; |
||||
} |
||||
|
||||
/** |
||||
* @param array|object|string $data |
||||
*/ |
||||
protected function preparePayload($data) |
||||
{ |
||||
$this->setOpt(CURLOPT_POST, true); |
||||
|
||||
if (is_array($data) || is_object($data)) { |
||||
$skip = false; |
||||
foreach ($data as $key => $value) { |
||||
// If a value is an instance of CurlFile skip the http_build_query |
||||
// see issue https://github.com/php-mod/curl/issues/46 |
||||
// suggestion from: https://stackoverflow.com/a/36603038/4611030 |
||||
if ($value instanceof \CurlFile) { |
||||
$skip = true; |
||||
} |
||||
} |
||||
|
||||
if (!$skip) { |
||||
$data = http_build_query($data); |
||||
} |
||||
} |
||||
|
||||
$this->setOpt(CURLOPT_POSTFIELDS, $data); |
||||
} |
||||
|
||||
/** |
||||
* Set auth options for the current request. |
||||
* |
||||
* Available auth types are: |
||||
* |
||||
* + self::AUTH_BASIC |
||||
* + self::AUTH_DIGEST |
||||
* + self::AUTH_GSSNEGOTIATE |
||||
* + self::AUTH_NTLM |
||||
* + self::AUTH_ANY |
||||
* + self::AUTH_ANYSAFE |
||||
* |
||||
* @param int $httpauth The type of authentication |
||||
*/ |
||||
protected function setHttpAuth($httpauth) |
||||
{ |
||||
$this->setOpt(CURLOPT_HTTPAUTH, $httpauth); |
||||
} |
||||
|
||||
// public methods |
||||
|
||||
/** |
||||
* @deprecated calling exec() directly is discouraged |
||||
*/ |
||||
public function _exec() |
||||
{ |
||||
return $this->exec(); |
||||
} |
||||
|
||||
// functions |
||||
|
||||
/** |
||||
* Make a get request with optional data. |
||||
* |
||||
* The get request has no body data, the data will be correctly added to the $url with the http_build_query() method. |
||||
* |
||||
* @param string $url The url to make the get request for |
||||
* @param array $data Optional arguments who are part of the url |
||||
* @return self |
||||
*/ |
||||
public function get($url, $data = array()) |
||||
{ |
||||
if (count($data) > 0) { |
||||
$this->setOpt(CURLOPT_URL, $url.'?'.http_build_query($data)); |
||||
} else { |
||||
$this->setOpt(CURLOPT_URL, $url); |
||||
} |
||||
$this->setOpt(CURLOPT_HTTPGET, true); |
||||
$this->exec(); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Make a post request with optional post data. |
||||
* |
||||
* @param string $url The url to make the post request |
||||
* @param array $data Post data to pass to the url |
||||
* @return self |
||||
*/ |
||||
public function post($url, $data = array()) |
||||
{ |
||||
$this->setOpt(CURLOPT_URL, $url); |
||||
$this->preparePayload($data); |
||||
$this->exec(); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Make a put request with optional data. |
||||
* |
||||
* The put request data can be either sent via payload or as get paramters of the string. |
||||
* |
||||
* @param string $url The url to make the put request |
||||
* @param array $data Optional data to pass to the $url |
||||
* @param bool $payload Whether the data should be transmitted trough payload or as get parameters of the string |
||||
* @return self |
||||
*/ |
||||
public function put($url, $data = array(), $payload = false) |
||||
{ |
||||
if (! empty($data)) { |
||||
if ($payload === false) { |
||||
$url .= '?'.http_build_query($data); |
||||
} else { |
||||
$this->preparePayload($data); |
||||
} |
||||
} |
||||
|
||||
$this->setOpt(CURLOPT_URL, $url); |
||||
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'PUT'); |
||||
$this->exec(); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Make a patch request with optional data. |
||||
* |
||||
* The patch request data can be either sent via payload or as get paramters of the string. |
||||
* |
||||
* @param string $url The url to make the patch request |
||||
* @param array $data Optional data to pass to the $url |
||||
* @param bool $payload Whether the data should be transmitted trough payload or as get parameters of the string |
||||
* @return self |
||||
*/ |
||||
public function patch($url, $data = array(), $payload = false) |
||||
{ |
||||
if (! empty($data)) { |
||||
if ($payload === false) { |
||||
$url .= '?'.http_build_query($data); |
||||
} else { |
||||
$this->preparePayload($data); |
||||
} |
||||
} |
||||
|
||||
$this->setOpt(CURLOPT_URL, $url); |
||||
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'PATCH'); |
||||
$this->exec(); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Make a delete request with optional data. |
||||
* |
||||
* @param string $url The url to make the delete request |
||||
* @param array $data Optional data to pass to the $url |
||||
* @param bool $payload Whether the data should be transmitted trough payload or as get parameters of the string |
||||
* @return self |
||||
*/ |
||||
public function delete($url, $data = array(), $payload = false) |
||||
{ |
||||
if (! empty($data)) { |
||||
if ($payload === false) { |
||||
$url .= '?'.http_build_query($data); |
||||
} else { |
||||
$this->preparePayload($data); |
||||
} |
||||
} |
||||
|
||||
$this->setOpt(CURLOPT_URL, $url); |
||||
$this->setOpt(CURLOPT_CUSTOMREQUEST, 'DELETE'); |
||||
$this->exec(); |
||||
return $this; |
||||
} |
||||
|
||||
// setters |
||||
|
||||
/** |
||||
* Pass basic auth data. |
||||
* |
||||
* If the the rquested url is secured by an httaccess basic auth mechanism you can use this method to provided the auth data. |
||||
* |
||||
* ```php |
||||
* $curl = new Curl(); |
||||
* $curl->setBasicAuthentication('john', 'doe'); |
||||
* $curl->get('http://example.com/secure.php'); |
||||
* ``` |
||||
* |
||||
* @param string $username The username for the authentification |
||||
* @param string $password The password for the given username for the authentification |
||||
* @return self |
||||
*/ |
||||
public function setBasicAuthentication($username, $password) |
||||
{ |
||||
$this->setHttpAuth(self::AUTH_BASIC); |
||||
$this->setOpt(CURLOPT_USERPWD, $username.':'.$password); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Provide optional header informations. |
||||
* |
||||
* In order to pass optional headers by key value pairing: |
||||
* |
||||
* ```php |
||||
* $curl = new Curl(); |
||||
* $curl->setHeader('X-Requested-With', 'XMLHttpRequest'); |
||||
* $curl->get('http://example.com/request.php'); |
||||
* ``` |
||||
* |
||||
* @param string $key The header key |
||||
* @param string $value The value for the given header key |
||||
* @return self |
||||
*/ |
||||
public function setHeader($key, $value) |
||||
{ |
||||
$this->_headers[$key] = $key.': '.$value; |
||||
$this->setOpt(CURLOPT_HTTPHEADER, array_values($this->_headers)); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Provide a User Agent. |
||||
* |
||||
* In order to provide you cusomtized user agent name you can use this method. |
||||
* |
||||
* ```php |
||||
* $curl = new Curl(); |
||||
* $curl->setUserAgent('My John Doe Agent 1.0'); |
||||
* $curl->get('http://example.com/request.php'); |
||||
* ``` |
||||
* |
||||
* @param string $useragent The name of the user agent to set for the current request |
||||
* @return self |
||||
*/ |
||||
public function setUserAgent($useragent) |
||||
{ |
||||
$this->setOpt(CURLOPT_USERAGENT, $useragent); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @deprecated Call setReferer() instead |
||||
*/ |
||||
public function setReferrer($referrer) |
||||
{ |
||||
$this->setReferer($referrer); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Set the HTTP referer header. |
||||
* |
||||
* The $referer informations can help identify the requested client where the requested was made. |
||||
* |
||||
* @param string $referer An url to pass and will be set as referer header |
||||
* @return self |
||||
*/ |
||||
public function setReferer($referer) |
||||
{ |
||||
$this->setOpt(CURLOPT_REFERER, $referer); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Set contents of HTTP Cookie header. |
||||
* |
||||
* @param string $key The name of the cookie |
||||
* @param string $value The value for the provided cookie name |
||||
* @return self |
||||
*/ |
||||
public function setCookie($key, $value) |
||||
{ |
||||
$this->_cookies[$key] = $value; |
||||
$this->setOpt(CURLOPT_COOKIE, http_build_query($this->_cookies, '', '; ')); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Set customized curl options. |
||||
* |
||||
* To see a full list of options: http://php.net/curl_setopt |
||||
* |
||||
* @see http://php.net/curl_setopt |
||||
* |
||||
* @param int $option The curl option constante e.g. `CURLOPT_AUTOREFERER`, `CURLOPT_COOKIESESSION` |
||||
* @param mixed $value The value to pass for the given $option |
||||
*/ |
||||
public function setOpt($option, $value) |
||||
{ |
||||
return curl_setopt($this->curl, $option, $value); |
||||
} |
||||
|
||||
/** |
||||
* Get customized curl options. |
||||
* |
||||
* To see a full list of options: http://php.net/curl_getinfo |
||||
* |
||||
* @see http://php.net/curl_getinfo |
||||
* |
||||
* @param int $option The curl option constante e.g. `CURLOPT_AUTOREFERER`, `CURLOPT_COOKIESESSION` |
||||
* @param mixed $value The value to check for the given $option |
||||
*/ |
||||
public function getOpt($option) |
||||
{ |
||||
return curl_getinfo($this->curl, $option); |
||||
} |
||||
|
||||
/** |
||||
* Return the endpoint set for curl |
||||
* |
||||
* @see http://php.net/curl_getinfo |
||||
* |
||||
* @return string of endpoint |
||||
*/ |
||||
public function getEndpoint() |
||||
{ |
||||
return $this->getOpt(CURLINFO_EFFECTIVE_URL); |
||||
} |
||||
|
||||
/** |
||||
* Enable verbositiy. |
||||
* |
||||
* @todo As to keep naming convention it should be renamed to `setVerbose()` |
||||
* |
||||
* @param string $on |
||||
* @return self |
||||
*/ |
||||
public function verbose($on = true) |
||||
{ |
||||
$this->setOpt(CURLOPT_VERBOSE, $on); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Reset all curl options. |
||||
* |
||||
* In order to make multiple requests with the same curl object all settings requires to be reset. |
||||
* @return self |
||||
*/ |
||||
public function reset() |
||||
{ |
||||
$this->close(); |
||||
$this->_cookies = array(); |
||||
$this->_headers = array(); |
||||
$this->error = false; |
||||
$this->error_code = 0; |
||||
$this->error_message = null; |
||||
$this->curl_error = false; |
||||
$this->curl_error_code = 0; |
||||
$this->curl_error_message = null; |
||||
$this->http_error = false; |
||||
$this->http_status_code = 0; |
||||
$this->http_error_message = null; |
||||
$this->request_headers = null; |
||||
$this->response_headers = array(); |
||||
$this->response = null; |
||||
$this->init(); |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Closing the current open curl resource. |
||||
* @return self |
||||
*/ |
||||
public function close() |
||||
{ |
||||
if (is_resource($this->curl)) { |
||||
curl_close($this->curl); |
||||
} |
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Close the connection when the Curl object will be destroyed. |
||||
*/ |
||||
public function __destruct() |
||||
{ |
||||
$this->close(); |
||||
} |
||||
|
||||
/** |
||||
* Was an 'info' header returned. |
||||
* @return bool |
||||
*/ |
||||
public function isInfo() |
||||
{ |
||||
return $this->http_status_code >= 100 && $this->http_status_code < 200; |
||||
} |
||||
|
||||
/** |
||||
* Was an 'OK' response returned. |
||||
* @return bool |
||||
*/ |
||||
public function isSuccess() |
||||
{ |
||||
return $this->http_status_code >= 200 && $this->http_status_code < 300; |
||||
} |
||||
|
||||
/** |
||||
* Was a 'redirect' returned. |
||||
* @return bool |
||||
*/ |
||||
public function isRedirect() |
||||
{ |
||||
return $this->http_status_code >= 300 && $this->http_status_code < 400; |
||||
} |
||||
|
||||
/** |
||||
* Was an 'error' returned (client error or server error). |
||||
* @return bool |
||||
*/ |
||||
public function isError() |
||||
{ |
||||
return $this->http_status_code >= 400 && $this->http_status_code < 600; |
||||
} |
||||
|
||||
/** |
||||
* Was a 'client error' returned. |
||||
* @return bool |
||||
*/ |
||||
public function isClientError() |
||||
{ |
||||
return $this->http_status_code >= 400 && $this->http_status_code < 500; |
||||
} |
||||
|
||||
/** |
||||
* Was a 'server error' returned. |
||||
* @return bool |
||||
*/ |
||||
public function isServerError() |
||||
{ |
||||
return $this->http_status_code >= 500 && $this->http_status_code < 600; |
||||
} |
||||
|
||||
/** |
||||
* Get a specific response header key or all values from the response headers array. |
||||
* |
||||
* Usage example: |
||||
* |
||||
* ```php |
||||
* $curl = (new Curl())->get('http://example.com'); |
||||
* |
||||
* echo $curl->getResponseHeaders('Content-Type'); |
||||
* ``` |
||||
* |
||||
* Or in order to dump all keys with the given values use: |
||||
* |
||||
* ```php |
||||
* $curl = (new Curl())->get('http://example.com'); |
||||
* |
||||
* var_dump($curl->getResponseHeaders()); |
||||
* ``` |
||||
* |
||||
* @param string $headerKey Optional key to get from the array. |
||||
* @return bool|string |
||||
* @since 1.9 |
||||
*/ |
||||
public function getResponseHeaders($headerKey = null) |
||||
{ |
||||
$headers = array(); |
||||
$headerKey = strtolower($headerKey); |
||||
|
||||
foreach ($this->response_headers as $header) { |
||||
$parts = explode(":", $header, 2); |
||||
|
||||
$key = isset($parts[0]) ? $parts[0] : null; |
||||
$value = isset($parts[1]) ? $parts[1] : null; |
||||
|
||||
$headers[trim(strtolower($key))] = trim($value); |
||||
} |
||||
|
||||
if ($headerKey) { |
||||
return isset($headers[$headerKey]) ? $headers[$headerKey] : false; |
||||
} |
||||
|
||||
return $headers; |
||||
} |
||||
|
||||
public function getResponse() |
||||
{ |
||||
return $this->response; |
||||
} |
||||
|
||||
public function getErrorCode() |
||||
{ |
||||
return $this->curl_error_code; |
||||
} |
||||
|
||||
public function getErrorMessage() |
||||
{ |
||||
return $this->curl_error_message; |
||||
} |
||||
|
||||
public function getHttpStatus() |
||||
{ |
||||
return $this->http_status_code; |
||||
} |
||||
} |
@ -0,0 +1,277 @@ |
||||
<?php |
||||
|
||||
namespace Curl; |
||||
|
||||
class CurlTest extends \PHPUnit_Framework_TestCase |
||||
{ |
||||
|
||||
const TEST_URL = 'http://server_test'; |
||||
|
||||
/** |
||||
* |
||||
* @var Curl |
||||
*/ |
||||
protected $curl; |
||||
|
||||
function setUp() { |
||||
$this->curl = new Curl(); |
||||
$this->curl->setOpt(CURLOPT_SSL_VERIFYPEER, FALSE); |
||||
$this->curl->setOpt(CURLOPT_SSL_VERIFYHOST, FALSE); |
||||
} |
||||
|
||||
function server($request_method, $data='') { |
||||
$request_method = strtolower($request_method); |
||||
$this->curl->$request_method(self::TEST_URL . '/server.php', $data); |
||||
return $this->curl->response; |
||||
} |
||||
|
||||
public function testExtensionLoaded() { |
||||
|
||||
$this->assertTrue(extension_loaded('curl')); |
||||
} |
||||
|
||||
public function testUserAgent() { |
||||
|
||||
$this->curl->setUserAgent(Curl::USER_AGENT); |
||||
$this->assertEquals(Curl::USER_AGENT, $this->server('GET', array( |
||||
'test' => 'server', |
||||
'key' => 'HTTP_USER_AGENT', |
||||
))); |
||||
|
||||
} |
||||
|
||||
public function testGet() { |
||||
$this->assertTrue($this->server('GET', array( |
||||
'test' => 'server', |
||||
'key' => 'REQUEST_METHOD', |
||||
)) === 'GET'); |
||||
} |
||||
|
||||
public function testPostRequestMethod() { |
||||
$this->assertTrue($this->server('POST', array( |
||||
'test' => 'server', |
||||
'key' => 'REQUEST_METHOD', |
||||
)) === 'POST'); |
||||
} |
||||
|
||||
public function testPostData() { |
||||
$this->assertTrue($this->server('POST', array( |
||||
'test' => 'post', |
||||
'key' => 'test', |
||||
)) === 'post'); |
||||
} |
||||
|
||||
public function testPostMultidimensionalData() { |
||||
|
||||
$data = array( |
||||
'key' => 'file', |
||||
'file' => array( |
||||
'wibble', |
||||
'wubble', |
||||
'wobble', |
||||
), |
||||
); |
||||
|
||||
$this->curl->post(self::TEST_URL . '/post_multidimensional.php', $data); |
||||
|
||||
$this->assertEquals( |
||||
'key=file&file%5B0%5D=wibble&file%5B1%5D=wubble&file%5B2%5D=wobble', |
||||
$this->curl->response); |
||||
|
||||
} |
||||
|
||||
public function testPostFilePathUpload() |
||||
{ |
||||
|
||||
$file_path = $this->get_png(); |
||||
|
||||
$data = array( |
||||
'key' => 'image', |
||||
'image' => '@' . $file_path, |
||||
); |
||||
|
||||
$this->curl->setOpt(CURLOPT_RETURNTRANSFER, true); |
||||
|
||||
$this->curl->post(self::TEST_URL . '/post_file_path_upload.php', $data); |
||||
|
||||
$this->assertEquals( |
||||
array( |
||||
'request_method' => 'POST', |
||||
'key' => 'image', |
||||
'mime_content_type' => 'ERROR', // Temp change the image response, but assuming this is not fixing the issue indeed. |
||||
//'mime_content_type' => 'image/png' |
||||
), |
||||
json_decode($this->curl->response, true)); |
||||
|
||||
unlink($file_path); |
||||
} |
||||
|
||||
public function testPutRequestMethod() { |
||||
$this->assertTrue($this->server('PUT', array( |
||||
'test' => 'server', |
||||
'key' => 'REQUEST_METHOD', |
||||
)) === 'PUT'); |
||||
} |
||||
|
||||
public function testPutData() { |
||||
$this->assertTrue($this->server('PUT', array( |
||||
'test' => 'put', |
||||
'key' => 'test', |
||||
)) === 'put'); |
||||
} |
||||
|
||||
public function testPutFileHandle() { |
||||
$png = $this->create_png(); |
||||
$tmp_file = $this->create_tmp_file($png); |
||||
|
||||
$this->curl->setOpt(CURLOPT_PUT, TRUE); |
||||
$this->curl->setOpt(CURLOPT_INFILE, $tmp_file); |
||||
$this->curl->setOpt(CURLOPT_INFILESIZE, strlen($png)); |
||||
$this->curl->put(self::TEST_URL . '/server.php', array( |
||||
'test' => 'put_file_handle', |
||||
)); |
||||
|
||||
fclose($tmp_file); |
||||
|
||||
$this->assertTrue($this->curl->response === 'image/png'); |
||||
} |
||||
|
||||
public function testDelete() { |
||||
$this->assertTrue($this->server('DELETE', array( |
||||
'test' => 'server', |
||||
'key' => 'REQUEST_METHOD', |
||||
)) === 'DELETE'); |
||||
|
||||
$this->assertTrue($this->server('DELETE', array( |
||||
'test' => 'delete', |
||||
'key' => 'test', |
||||
)) === 'delete'); |
||||
} |
||||
|
||||
public function testBasicHttpAuth() { |
||||
|
||||
$data = array(); |
||||
|
||||
$this->curl->get(self::TEST_URL . '/http_basic_auth.php', $data); |
||||
|
||||
$this->assertEquals('canceled', $this->curl->response); |
||||
|
||||
$username = 'myusername'; |
||||
$password = 'mypassword'; |
||||
|
||||
$this->curl->setBasicAuthentication($username, $password); |
||||
|
||||
$this->curl->get(self::TEST_URL . '/http_basic_auth.php', $data); |
||||
|
||||
$this->assertEquals( |
||||
'{"username":"myusername","password":"mypassword"}', |
||||
$this->curl->response); |
||||
} |
||||
|
||||
public function testReferrer() { |
||||
$this->curl->setReferer('myreferrer'); |
||||
$this->assertTrue($this->server('GET', array( |
||||
'test' => 'server', |
||||
'key' => 'HTTP_REFERER', |
||||
)) === 'myreferrer'); |
||||
} |
||||
|
||||
public function testDeprecatedReferrer() { |
||||
$this->curl->setReferrer('myreferrer'); |
||||
$this->assertTrue($this->server('GET', array( |
||||
'test' => 'server', |
||||
'key' => 'HTTP_REFERER', |
||||
)) === 'myreferrer'); |
||||
} |
||||
|
||||
public function testCookies() { |
||||
$this->curl->setCookie('mycookie', 'yum'); |
||||
$this->assertTrue($this->server('GET', array( |
||||
'test' => 'cookie', |
||||
'key' => 'mycookie', |
||||
)) === 'yum'); |
||||
} |
||||
|
||||
public function testError() { |
||||
$this->curl->setOpt(CURLOPT_CONNECTTIMEOUT_MS, 2000); |
||||
$this->curl->get('http://1.2.3.4/'); |
||||
$this->assertTrue($this->curl->error === TRUE); |
||||
$this->assertTrue($this->curl->curl_error === TRUE); |
||||
$this->assertTrue($this->curl->curl_error_code === CURLE_OPERATION_TIMEOUTED); |
||||
} |
||||
|
||||
public function testHeaders() { |
||||
$this->curl->setHeader('Content-Type', 'application/json'); |
||||
$this->curl->setHeader('X-Requested-With', 'XMLHttpRequest'); |
||||
$this->curl->setHeader('Accept', 'application/json'); |
||||
$this->assertTrue($this->server('GET', array( |
||||
'test' => 'server', |
||||
'key' => 'CONTENT_TYPE', |
||||
)) === 'application/json'); |
||||
$this->assertTrue($this->server('GET', array( |
||||
'test' => 'server', |
||||
'key' => 'HTTP_X_REQUESTED_WITH', |
||||
)) === 'XMLHttpRequest'); |
||||
$this->assertTrue($this->server('GET', array( |
||||
'test' => 'server', |
||||
'key' => 'HTTP_ACCEPT', |
||||
)) === 'application/json'); |
||||
} |
||||
|
||||
public function testHeadersWithContinue() { |
||||
$headers = file(dirname(__FILE__) . '/data/response_headers_with_continue.txt'); |
||||
|
||||
$this->curl->response_headers = array(); |
||||
foreach($headers as $header_line) { |
||||
$this->curl->addResponseHeaderLine(null, $header_line); |
||||
} |
||||
|
||||
$expected_headers = array_values(array_filter(array_map(function($l) { return trim($l, "\r\n"); }, array_slice($headers, 1)))); |
||||
|
||||
$this->assertEquals($expected_headers, $this->curl->response_headers); |
||||
} |
||||
|
||||
public function testReset() |
||||
{ |
||||
$curl = $this->getMockBuilder(get_class($this->curl))->getMock(); |
||||
$curl->expects($this->once())->method('reset')->with(); |
||||
// lets make small request |
||||
$curl->setOpt(CURLOPT_CONNECTTIMEOUT_MS, 2000); |
||||
$curl->get('http://1.2.3.4/'); |
||||
$curl->reset(); |
||||
$this->assertFalse($curl->error); |
||||
$this->assertSame(0, $curl->error_code); |
||||
$this->assertNull($curl->error_message); |
||||
$this->assertFalse($curl->curl_error); |
||||
$this->assertSame(0, $curl->curl_error_code); |
||||
$this->assertNull($curl->curl_error_message); |
||||
$this->assertFalse($curl->http_error); |
||||
$this->assertSame(0, $curl->http_status_code); |
||||
$this->assertNull($curl->http_error_message); |
||||
$this->assertNull($curl->request_headers); |
||||
$this->assertEmpty($curl->response_headers); |
||||
$this->assertNull($curl->response); |
||||
} |
||||
|
||||
function create_png() { |
||||
// PNG image data, 1 x 1, 1-bit colormap, non-interlaced |
||||
ob_start(); |
||||
imagepng(imagecreatefromstring(base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'))); |
||||
$raw_image = ob_get_contents(); |
||||
ob_end_clean(); |
||||
return $raw_image; |
||||
} |
||||
|
||||
function create_tmp_file($data) { |
||||
$tmp_file = tmpfile(); |
||||
fwrite($tmp_file, $data); |
||||
rewind($tmp_file); |
||||
return $tmp_file; |
||||
} |
||||
|
||||
function get_png() { |
||||
$tmp_filename = tempnam('/tmp', 'php-curl-class.'); |
||||
file_put_contents($tmp_filename, $this->create_png()); |
||||
return $tmp_filename; |
||||
} |
||||
} |
@ -0,0 +1,13 @@ |
||||
HTTP/1.1 100 Continue |
||||
|
||||
HTTP/1.1 200 OK |
||||
Server: nginx/1.1.19 |
||||
Date: Fri, 11 Aug 2017 13:22:00 GMT |
||||
Content-Type: image/jpeg |
||||
Content-Length: 62574 |
||||
Connection: close |
||||
Cache-Control: max-age=7257600 |
||||
Expires: Fri, 03 Nov 2017 13:22:00 GMT |
||||
Strict-Transport-Security: max-age=31536000; includeSubDomains |
||||
X-Frame-Option: DENY |
||||
|
After Width: | Height: | Size: 2.8 KiB |
@ -0,0 +1,9 @@ |
||||
FROM alpine:3.7 |
||||
|
||||
RUN apk add --no-cache php5-cli php5-curl php5-gd php5-phar php5-json php5-openssl php5-dom |
||||
|
||||
COPY php-curl-test php-curl-test |
||||
|
||||
EXPOSE 80 |
||||
|
||||
CMD ["php5", "-S", "0.0.0.0:80", "-t", "php-curl-test"] |
@ -0,0 +1,37 @@ |
||||
<?php |
||||
|
||||
// The commands |
||||
$commands = array( |
||||
'cd ../../.. && git pull', |
||||
); |
||||
|
||||
// Run the commands for output |
||||
$output = ''; |
||||
foreach($commands AS $command){ |
||||
// Run it |
||||
$tmp = shell_exec($command); |
||||
// Output |
||||
$output .= "<span style=\"color: #6BE234;\">\$</span> <span style=\"color: #729FCF;\">{$command}\n</span>"; |
||||
$output .= htmlentities(trim($tmp)) . "\n"; |
||||
} |
||||
|
||||
// Make it pretty for manual user access (and why not?) |
||||
?> |
||||
<!DOCTYPE HTML> |
||||
<html lang="en-US"> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<title>GIT DEPLOYMENT SCRIPT</title> |
||||
</head> |
||||
<body style="background-color: #000000; color: #FFFFFF; font-weight: bold; padding: 0 10px;"> |
||||
<pre> |
||||
. ____ . ____________________________ |
||||
|/ \| | | |
||||
[| <span style="color: #FF0000;">♥ ♥</span> |] | Git Deployment Script v0.1 | |
||||
|___==___| / © oodavid 2012 | |
||||
|____________________________| |
||||
|
||||
<?php echo $output; ?> |
||||
</pre> |
||||
</body> |
||||
</html> |
@ -0,0 +1,14 @@ |
||||
<?php |
||||
|
||||
if (!isset($_SERVER['PHP_AUTH_USER'])) { |
||||
header('WWW-Authenticate: Basic realm="My Realm"'); |
||||
header('HTTP/1.0 401 Unauthorized'); |
||||
echo 'canceled'; |
||||
exit; |
||||
} |
||||
|
||||
header('Content-Type: application/json'); |
||||
echo json_encode(array( |
||||
'username' => $_SERVER['PHP_AUTH_USER'], |
||||
'password' => $_SERVER['PHP_AUTH_PW'], |
||||
)); |
@ -0,0 +1,21 @@ |
||||
<?php |
||||
|
||||
$request_method = isset($_SERVER['REQUEST_METHOD']) ? |
||||
$_SERVER['REQUEST_METHOD'] : ''; |
||||
|
||||
$data_values = $request_method === 'POST' ? $_POST : $_GET; |
||||
|
||||
$key = isset($data_values['key']) ? $data_values['key'] : ''; |
||||
|
||||
$response = array(); |
||||
|
||||
$response['request_method'] = $request_method; |
||||
$response['key'] = $key; |
||||
|
||||
if(isset($_FILES[$key])) { |
||||
$response['mime_content_type'] = mime_content_type($_FILES[$key]['tmp_name']); |
||||
} else { |
||||
$response['mime_content_type'] = 'ERROR'; |
||||
} |
||||
|
||||
echo json_encode($response); |
@ -0,0 +1,4 @@ |
||||
<?php |
||||
|
||||
$http_raw_post_data = file_get_contents('php://input'); |
||||
echo $http_raw_post_data; |
@ -0,0 +1,31 @@ |
||||
<?php |
||||
$request_method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : ''; |
||||
$data_values = $request_method === 'POST' ? $_POST : $_GET; |
||||
$test = isset($data_values['test']) ? $data_values['test'] : ''; |
||||
$key = isset($data_values['key']) ? $data_values['key'] : ''; |
||||
|
||||
if ($test === 'put_file_handle') { |
||||
$tmp_filename = tempnam('/tmp', 'php-curl-class.'); |
||||
file_put_contents($tmp_filename, file_get_contents('php://input')); |
||||
echo mime_content_type($tmp_filename); |
||||
unlink($tmp_filename); |
||||
exit; |
||||
} |
||||
|
||||
header('Content-Type: text/plain'); |
||||
|
||||
$data_mapping = array( |
||||
'cookie' => '_COOKIE', |
||||
'delete' => '_GET', |
||||
'post' => '_POST', |
||||
'put' => '_GET', |
||||
'server' => '_SERVER', |
||||
); |
||||
|
||||
if(isset($data_mapping[$test])) { |
||||
$data = ${$data_mapping[$test]}; |
||||
$value = isset($data[$key]) ? $data[$key] : ''; |
||||
echo $value; |
||||
} else { |
||||
echo "Error."; |
||||
} |
@ -0,0 +1,52 @@ |
||||
# phpspider -- PHP蜘蛛爬虫框架 |
||||
《我用爬虫一天时间“偷了”知乎一百万用户,只为证明PHP是世界上最好的语言 》所使用的程序 |
||||
|
||||
phpspider是一个爬虫开发框架。使用本框架,你不用了解爬虫的底层技术实现,爬虫被网站屏蔽、有些网站需要登录或验证码识别才能爬取等问题。简单几行PHP代码,就可以创建自己的爬虫,利用框架封装的多进程Worker类库,代码更简洁,执行效率更高速度更快。 |
||||
|
||||
demo目录下有一些特定网站的爬取规则,只要你安装了PHP环境,代码就可以在命令行下直接跑。 对爬虫感兴趣的开发者可以加QQ群一起讨论:147824717。 |
||||
|
||||
下面以糗事百科为例, 来看一下我们的爬虫长什么样子: |
||||
|
||||
``` |
||||
$configs = array( |
||||
'name' => '糗事百科', |
||||
'domains' => array( |
||||
'qiushibaike.com', |
||||
'www.qiushibaike.com' |
||||
), |
||||
'scan_urls' => array( |
||||
'http://www.qiushibaike.com/' |
||||
), |
||||
'content_url_regexes' => array( |
||||
"http://www.qiushibaike.com/article/\d+" |
||||
), |
||||
'list_url_regexes' => array( |
||||
"http://www.qiushibaike.com/8hr/page/\d+\?s=\d+" |
||||
), |
||||
'fields' => array( |
||||
array( |
||||
// 抽取内容页的文章内容 |
||||
'name' => "article_content", |
||||
'selector' => "//*[@id='single-next-link']", |
||||
'required' => true |
||||
), |
||||
array( |
||||
// 抽取内容页的文章作者 |
||||
'name' => "article_author", |
||||
'selector' => "//div[contains(@class,'author')]//h2", |
||||
'required' => true |
||||
), |
||||
), |
||||
); |
||||
$spider = new phpspider($configs); |
||||
$spider->start(); |
||||
``` |
||||
爬虫的整体框架就是这样, 首先定义了一个$configs数组, 里面设置了待爬网站的一些信息, 然后通过调用```$spider = new phpspider($configs);```和```$spider->start();```来配置并启动爬虫. |
||||
|
||||
#### 运行界面如下: |
||||
|
||||
 |
||||
|
||||
更多详细内容,移步到: |
||||
|
||||
[开发文档](http://doc.phpspider.org) |
@ -0,0 +1,77 @@ |
||||
<?php |
||||
/** |
||||
* This file is part of phpspider. |
||||
* |
||||
* Licensed under The MIT License |
||||
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
* Redistributions of files must retain the above copyright notice. |
||||
* |
||||
* @author seatle<seatle@foxmail.com> |
||||
* @copyright seatle<seatle@foxmail.com> |
||||
* @link http://www.phpspider.org/ |
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
*/ |
||||
namespace phpspider; |
||||
|
||||
/** |
||||
* autoloader. |
||||
*/ |
||||
class autoloader |
||||
{ |
||||
/** |
||||
* Autoload root path. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected static $_autoload_root_path = ''; |
||||
|
||||
/** |
||||
* Set autoload root path. |
||||
* |
||||
* @param string $root_path |
||||
* @return void |
||||
*/ |
||||
public static function set_root_path($root_path) |
||||
{ |
||||
self::$_autoload_root_path = $root_path; |
||||
} |
||||
|
||||
/** |
||||
* Load files by namespace. |
||||
* |
||||
* @param string $name |
||||
* @return boolean |
||||
*/ |
||||
public static function load_by_namespace($name) |
||||
{ |
||||
$class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name); |
||||
|
||||
if (strpos($name, 'phpspider\\') === 0) |
||||
{ |
||||
$class_file = __DIR__ . substr($class_path, strlen('phpspider')) . '.php'; |
||||
} |
||||
else |
||||
{ |
||||
if (self::$_autoload_root_path) |
||||
{ |
||||
$class_file = self::$_autoload_root_path . DIRECTORY_SEPARATOR . $class_path . '.php'; |
||||
} |
||||
if (empty($class_file) || !is_file($class_file)) |
||||
{ |
||||
$class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php"; |
||||
} |
||||
} |
||||
|
||||
if (is_file($class_file)) |
||||
{ |
||||
require_once($class_file); |
||||
if (class_exists($name, false)) |
||||
{ |
||||
return true; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
spl_autoload_register('\phpspider\autoloader::load_by_namespace'); |
@ -0,0 +1,38 @@ |
||||
{ |
||||
"name": "owner888/phpspider", |
||||
"type": "library", |
||||
"keywords": [ |
||||
"framework", |
||||
"phpspider" |
||||
], |
||||
"homepage": "http://www.phpspider.org", |
||||
"license": "MIT", |
||||
"description": "The PHPSpider Framework.", |
||||
"authors": [ |
||||
{ |
||||
"name": "Seatle Yang", |
||||
"email": "seatle@foxmail.com", |
||||
"homepage": "http://www.phpspider.org", |
||||
"role": "Developer" |
||||
} |
||||
], |
||||
"support": { |
||||
"email": "seatle@foxmail.com", |
||||
"issues": "https://github.com/owner888/phpspider/issues", |
||||
"forum": "http://wenda.phpspider.org/", |
||||
"wiki": "http://doc.phpspider.org/", |
||||
"source": "https://github.com/owner888/phpspider" |
||||
}, |
||||
"require": { |
||||
"php": ">=5.5.0" |
||||
}, |
||||
"suggest": { |
||||
"ext-pcntl、ext-redis": "For better performance. " |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"phpspider\\": "./" |
||||
} |
||||
}, |
||||
"minimum-stability": "dev" |
||||
} |
@ -0,0 +1,64 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// PHPSpider缓存类文件 |
||||
//---------------------------------- |
||||
|
||||
class cache |
||||
{ |
||||
// 多进程下面不能用单例模式 |
||||
//protected static $_instance; |
||||
/** |
||||
* 获取实例 |
||||
* |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-04-10 22:55 |
||||
*/ |
||||
public static function init() |
||||
{ |
||||
if(extension_loaded('Redis')) |
||||
{ |
||||
$_instance = new Redis(); |
||||
} |
||||
else |
||||
{ |
||||
$errmsg = "extension redis is not installed"; |
||||
log::add($errmsg, "Error"); |
||||
return null; |
||||
} |
||||
// 这里不能用pconnect,会报错:Uncaught exception 'RedisException' with message 'read error on connection' |
||||
$_instance->connect($GLOBALS['config']['redis']['host'], $GLOBALS['config']['redis']['port'], $GLOBALS['config']['redis']['timeout']); |
||||
|
||||
// 验证 |
||||
if ($GLOBALS['config']['redis']['pass']) |
||||
{ |
||||
if ( !$_instance->auth($GLOBALS['config']['redis']['pass']) ) |
||||
{ |
||||
$errmsg = "Redis Server authentication failed!!"; |
||||
log::add($errmsg, "Error"); |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
// 不序列化的话不能存数组,用php的序列化方式其他语言又不能读取,所以这里自己用json序列化了,性能还比php的序列化好1.4倍 |
||||
//$_instance->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_NONE); // don't serialize data |
||||
//$_instance->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP); // use built-in serialize/unserialize |
||||
//$_instance->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_IGBINARY); // use igBinary serialize/unserialize |
||||
|
||||
$_instance->setOption(Redis::OPT_PREFIX, $GLOBALS['config']['redis']['prefix'] . ":"); |
||||
|
||||
return $_instance; |
||||
} |
||||
} |
||||
|
||||
|
@ -0,0 +1,55 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// PHPSpider公共入口文件 |
||||
//---------------------------------- |
||||
|
||||
//namespace phpspider\core; |
||||
|
||||
// Display errors. |
||||
ini_set('display_errors', 'on'); |
||||
// Reporting all. |
||||
error_reporting(E_ALL); |
||||
|
||||
// 永不超时 |
||||
ini_set('max_execution_time', 0); |
||||
set_time_limit(0); |
||||
// 内存限制,如果外面设置的内存比 /etc/php/php-cli.ini 大,就不要设置了 |
||||
if (intval(ini_get("memory_limit")) < 1024) |
||||
{ |
||||
ini_set('memory_limit', '1024M'); |
||||
} |
||||
|
||||
if( PHP_SAPI != 'cli' ) |
||||
{ |
||||
exit("You must run the CLI environment\n"); |
||||
} |
||||
|
||||
// Date.timezone |
||||
if (!ini_get('date.timezone')) |
||||
{ |
||||
date_default_timezone_set('Asia/Shanghai'); |
||||
} |
||||
|
||||
//核心库目录 |
||||
define('CORE', dirname(__FILE__)); |
||||
define('PATH_ROOT', CORE."/../"); |
||||
define('PATH_DATA', CORE."/../data"); |
||||
define('PATH_LIBRARY', CORE."/../library"); |
||||
|
||||
//系统配置 |
||||
//if( file_exists( PATH_ROOT."/config/inc_config.php" ) ) |
||||
//{ |
||||
//require PATH_ROOT."/config/inc_config.php"; |
||||
//} |
||||
|
||||
|
@ -0,0 +1,579 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// PHPSpider数据库类文件 |
||||
//---------------------------------- |
||||
|
||||
namespace phpspider\core; |
||||
|
||||
class db |
||||
{ |
||||
private static $configs = array(); |
||||
private static $rsid; |
||||
private static $links = array(); |
||||
private static $link_name = 'default'; |
||||
private static $autocommiting = false; |
||||
|
||||
public static function _init() |
||||
{ |
||||
// 获取配置 |
||||
$config = self::$link_name == 'default' ? self::_get_default_config() : self::$configs[self::$link_name]; |
||||
|
||||
// 创建连接 |
||||
if (empty(self::$links[self::$link_name]) || empty(self::$links[self::$link_name]['conn'])) |
||||
{ |
||||
// 第一次连接,初始化fail和pid |
||||
if (empty(self::$links[self::$link_name])) |
||||
{ |
||||
self::$links[self::$link_name]['fail'] = 0; |
||||
self::$links[self::$link_name]['pid'] = function_exists('posix_getpid') ? posix_getpid() : 0; |
||||
//echo "progress[".self::$links[self::$link_name]['pid']."] create db connect[".self::$link_name."]\n"; |
||||
} |
||||
self::$links[self::$link_name]['conn'] = mysqli_connect($config['host'], $config['user'], $config['pass'], $config['name'], $config['port']); |
||||
if(mysqli_connect_errno()) |
||||
{ |
||||
self::$links[self::$link_name]['fail']++; |
||||
$errmsg = 'Mysql Connect failed['.self::$links[self::$link_name]['fail'].']: ' . mysqli_connect_error(); |
||||
echo util::colorize(date("H:i:s") . " {$errmsg}\n\n", 'fail'); |
||||
log::add($errmsg, "Error"); |
||||
// 连接失败5次,中断进程 |
||||
if (self::$links[self::$link_name]['fail'] >= 5) |
||||
{ |
||||
exit(250); |
||||
} |
||||
self::_init($config); |
||||
} |
||||
else |
||||
{ |
||||
mysqli_query(self::$links[self::$link_name]['conn'], " SET character_set_connection=utf8, character_set_results=utf8, character_set_client=binary, sql_mode='' "); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
$curr_pid = function_exists('posix_getpid') ? posix_getpid() : 0; |
||||
// 如果父进程已经生成资源就释放重新生成,因为多进程不能共享连接资源 |
||||
if (self::$links[self::$link_name]['pid'] != $curr_pid) |
||||
{ |
||||
self::clear_link(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 重新设置连接 |
||||
* 传空的话就等于关闭数据库再连接 |
||||
* 在多进程环境下如果主进程已经调用过了,子进程一定要调用一次 clear_link,否则会报错: |
||||
* Error while reading greeting packet. PID=19615,这是两个进程互抢一个连接句柄引起的 |
||||
* |
||||
* @param array $config |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-03-29 00:51 |
||||
*/ |
||||
public static function clear_link() |
||||
{ |
||||
if(self::$links) |
||||
{ |
||||
foreach(self::$links as $k=>$v) |
||||
{ |
||||
@mysqli_close($v['conn']); |
||||
unset(self::$links[$k]); |
||||
} |
||||
} |
||||
// 注意,只会连接最后一个,不过貌似也够用了啊 |
||||
self::_init(); |
||||
} |
||||
|
||||
/** |
||||
* 改变链接为指定配置的链接(如果不同时使用多个数据库,不会涉及这个操作) |
||||
* @parem $link_name 链接标识名 |
||||
* @parem $config 多次使用时, 这个数组只需传递一次 |
||||
* config 格式与 $GLOBALS['config']['db'] 一致 |
||||
* @return void |
||||
*/ |
||||
public static function set_connect($link_name, $config = array()) |
||||
{ |
||||
self::$link_name = $link_name; |
||||
if (!empty($config)) |
||||
{ |
||||
self::$configs[self::$link_name] = $config; |
||||
} |
||||
else |
||||
{ |
||||
if (empty(self::$configs[self::$link_name])) |
||||
{ |
||||
throw new Exception("You not set a config array for connect!"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 还原为默认连接(如果不同时使用多个数据库,不会涉及这个操作) |
||||
* @parem $config 指定配置(默认使用inc_config.php的配置) |
||||
* @return void |
||||
*/ |
||||
public static function set_connect_default() |
||||
{ |
||||
$config = self::_get_default_config(); |
||||
self::set_connect('default', $config); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* 获取默认配置 |
||||
*/ |
||||
protected static function _get_default_config() |
||||
{ |
||||
if (empty(self::$configs['default'])) |
||||
{ |
||||
if (!is_array($GLOBALS['config']['db'])) |
||||
{ |
||||
exit('db.php _get_default_config()' . '没有mysql配置'); |
||||
} |
||||
self::$configs['default'] = $GLOBALS['config']['db']; |
||||
} |
||||
return self::$configs['default']; |
||||
} |
||||
|
||||
/** |
||||
* 返回查询游标 |
||||
* @return rsid |
||||
*/ |
||||
protected static function _get_rsid($rsid = '') |
||||
{ |
||||
return $rsid == '' ? self::$rsid : $rsid; |
||||
} |
||||
|
||||
public static function autocommit($mode = false) |
||||
{ |
||||
if ( self::$autocommiting ) |
||||
{ |
||||
return true; |
||||
} |
||||
|
||||
self::$autocommiting = true; |
||||
|
||||
self::_init(); |
||||
return mysqli_autocommit(self::$links[self::$link_name]['conn'], $mode); |
||||
} |
||||
|
||||
public static function begin_tran() |
||||
{ |
||||
return self::autocommit(false); |
||||
} |
||||
|
||||
public static function commit() |
||||
{ |
||||
mysqli_commit(self::$links[self::$link_name]['conn']); |
||||
self::autocommit(true); |
||||
return true; |
||||
} |
||||
|
||||
|
||||
public static function rollback() |
||||
{ |
||||
mysqli_rollback(self::$links[self::$link_name]['conn']); |
||||
self::autocommit(true); |
||||
return true; |
||||
} |
||||
|
||||
public static function query($sql) |
||||
{ |
||||
$sql = trim($sql); |
||||
|
||||
// 初始化数据库 |
||||
self::_init(); |
||||
self::$rsid = @mysqli_query(self::$links[self::$link_name]['conn'], $sql); |
||||
|
||||
if (self::$rsid === false) |
||||
{ |
||||
// 不要每次都ping,浪费流量浪费性能,执行出错了才重新连接 |
||||
$errno = mysqli_errno(self::$links[self::$link_name]['conn']); |
||||
if ($errno == 2013 || $errno == 2006) |
||||
{ |
||||
$errmsg = mysqli_error(self::$links[self::$link_name]['conn']); |
||||
log::add($errmsg, "Error"); |
||||
|
||||
@mysqli_close(self::$links[self::$link_name]['conn']); |
||||
self::$links[self::$link_name]['conn'] = null; |
||||
return self::query($sql); |
||||
} |
||||
|
||||
$errmsg = "Query SQL: ".$sql; |
||||
log::add($errmsg, "Warning"); |
||||
$errmsg = "Error SQL: ".mysqli_error(self::$links[self::$link_name]['conn']); |
||||
log::add($errmsg, "Warning"); |
||||
|
||||
$backtrace = debug_backtrace(); |
||||
array_shift($backtrace); |
||||
$narr = array('class', 'type', 'function', 'file', 'line'); |
||||
$err = "debug_backtrace:\n"; |
||||
foreach($backtrace as $i => $l) |
||||
{ |
||||
foreach($narr as $k) |
||||
{ |
||||
if( !isset($l[$k]) ) |
||||
{ |
||||
$l[$k] = ''; |
||||
} |
||||
} |
||||
$err .= "[$i] in function {$l['class']}{$l['type']}{$l['function']} "; |
||||
if($l['file']) $err .= " in {$l['file']} "; |
||||
if($l['line']) $err .= " on line {$l['line']} "; |
||||
$err .= "\n"; |
||||
} |
||||
log::add($err); |
||||
|
||||
return false; |
||||
} |
||||
else |
||||
{ |
||||
return self::$rsid; |
||||
} |
||||
} |
||||
|
||||
public static function fetch($rsid = '') |
||||
{ |
||||
$rsid = self::_get_rsid($rsid); |
||||
$row = mysqli_fetch_array($rsid, MYSQLI_ASSOC); |
||||
return $row; |
||||
} |
||||
|
||||
public static function get_one($sql) |
||||
{ |
||||
if (!preg_match("/limit/i", $sql)) |
||||
{ |
||||
$sql = preg_replace("/[,;]$/i", '', trim($sql)) . " limit 1 "; |
||||
} |
||||
$rsid = self::query($sql); |
||||
if ($rsid === false) |
||||
{ |
||||
return array(); |
||||
} |
||||
$row = self::fetch($rsid); |
||||
self::free($rsid); |
||||
return $row; |
||||
} |
||||
|
||||
public static function get_all($sql) |
||||
{ |
||||
$rsid = self::query($sql); |
||||
if ($rsid === false) |
||||
{ |
||||
return array(); |
||||
} |
||||
while ( $row = self::fetch($rsid) ) |
||||
{ |
||||
$rows[] = $row; |
||||
} |
||||
self::free($rsid); |
||||
return empty($rows) ? false : $rows; |
||||
} |
||||
|
||||
public static function free($rsid) |
||||
{ |
||||
return mysqli_free_result($rsid); |
||||
} |
||||
|
||||
public static function insert_id() |
||||
{ |
||||
return mysqli_insert_id(self::$links[self::$link_name]['conn']); |
||||
} |
||||
|
||||
public static function affected_rows() |
||||
{ |
||||
return mysqli_affected_rows(self::$links[self::$link_name]['conn']); |
||||
} |
||||
|
||||
public static function insert($table = '', $data = null, $return_sql = false) |
||||
{ |
||||
$items_sql = $values_sql = ""; |
||||
foreach ($data as $k => $v) |
||||
{ |
||||
$v = stripslashes($v); |
||||
$v = addslashes($v); |
||||
$items_sql .= "`$k`,"; |
||||
$values_sql .= "\"$v\","; |
||||
} |
||||
$sql = "Insert Ignore Into `{$table}` (" . substr($items_sql, 0, -1) . ") Values (" . substr($values_sql, 0, -1) . ")"; |
||||
if ($return_sql) |
||||
{ |
||||
return $sql; |
||||
} |
||||
else |
||||
{ |
||||
if (self::query($sql)) |
||||
{ |
||||
return mysqli_insert_id(self::$links[self::$link_name]['conn']); |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static function insert_batch($table = '', $set = NULL, $return_sql = FALSE) |
||||
{ |
||||
if (empty($table) || empty($set)) |
||||
{ |
||||
return false; |
||||
} |
||||
$set = self::strsafe($set); |
||||
$fields = self::get_fields($table); |
||||
|
||||
$keys_sql = $vals_sql = array(); |
||||
foreach ($set as $i=>$val) |
||||
{ |
||||
ksort($val); |
||||
$vals = array(); |
||||
foreach ($val as $k => $v) |
||||
{ |
||||
// 过滤掉数据库没有的字段 |
||||
if (!in_array($k, $fields)) |
||||
{ |
||||
continue; |
||||
} |
||||
// 如果是第一个数组,把key当做插入条件 |
||||
if ($i == 0 && $k == 0) |
||||
{ |
||||
$keys_sql[] = "`$k`"; |
||||
} |
||||
$vals[] = "\"$v\""; |
||||
} |
||||
$vals_sql[] = implode(",", $vals); |
||||
} |
||||
|
||||
$sql = "Insert Ignore Into `{$table}`(".implode(", ", $keys_sql).") Values (".implode("), (", $vals_sql).")"; |
||||
|
||||
if ($return_sql) return $sql; |
||||
|
||||
$rt = self::query($sql); |
||||
$insert_id = self::insert_id(); |
||||
$return = empty($insert_id) ? $rt : $insert_id; |
||||
return $return; |
||||
} |
||||
|
||||
public static function update_batch($table = '', $set = NULL, $index = NULL, $where = NULL, $return_sql = FALSE) |
||||
{ |
||||
if (empty($table) || is_null($set) || is_null($index)) |
||||
{ |
||||
// 不要用exit,会中断程序 |
||||
return false; |
||||
} |
||||
$set = self::strsafe($set); |
||||
$fields = self::get_fields($table); |
||||
|
||||
$ids = array(); |
||||
foreach ($set as $val) |
||||
{ |
||||
ksort($val); |
||||
// 去重,其实不去也可以,因为相同的when只会执行第一个,后面的就直接跳过不执行了 |
||||
$key = md5($val[$index]); |
||||
$ids[$key] = $val[$index]; |
||||
|
||||
foreach (array_keys($val) as $field) |
||||
{ |
||||
if ($field != $index) |
||||
{ |
||||
$final[$field][$key] = 'When `'.$index.'` = "'.$val[$index].'" Then "'.$val[$field].'"'; |
||||
} |
||||
} |
||||
} |
||||
//$ids = array_values($ids); |
||||
|
||||
// 如果不是数组而且不为空,就转数组 |
||||
if (!is_array($where) && !empty($where)) |
||||
{ |
||||
$where = array($where); |
||||
} |
||||
$where[] = $index.' In ("'.implode('","', $ids).'")'; |
||||
$where = empty($where) ? "" : " Where ".implode(" And ", $where); |
||||
|
||||
$sql = "Update `".$table."` Set "; |
||||
$cases = ''; |
||||
|
||||
foreach ($final as $k => $v) |
||||
{ |
||||
// 过滤掉数据库没有的字段 |
||||
if (!in_array($k, $fields)) |
||||
{ |
||||
continue; |
||||
} |
||||
$cases .= '`'.$k.'` = Case '."\n"; |
||||
foreach ($v as $row) |
||||
{ |
||||
$cases .= $row."\n"; |
||||
} |
||||
|
||||
$cases .= 'Else `'.$k.'` End, '; |
||||
} |
||||
|
||||
$sql .= substr($cases, 0, -2); |
||||
|
||||
// 其实不带 Where In ($index) 的条件也可以的 |
||||
$sql .= $where; |
||||
|
||||
if ($return_sql) return $sql; |
||||
|
||||
$rt = self::query($sql); |
||||
$insert_id = self::affected_rows(); |
||||
$return = empty($affected_rows) ? $rt : $affected_rows; |
||||
return $return; |
||||
} |
||||
|
||||
public static function update($table = '', $data = array(), $where = null, $return_sql = false) |
||||
{ |
||||
$sql = "UPDATE `{$table}` SET "; |
||||
foreach ($data as $k => $v) |
||||
{ |
||||
$v = stripslashes($v); |
||||
$v = addslashes($v); |
||||
$sql .= "`{$k}` = \"{$v}\","; |
||||
} |
||||
if (!is_array($where)) |
||||
{ |
||||
$where = array($where); |
||||
} |
||||
// 删除空字段,不然array("")会成为WHERE |
||||
foreach ($where as $k => $v) |
||||
{ |
||||
if (empty($v)) |
||||
{ |
||||
unset($where[$k]); |
||||
} |
||||
} |
||||
$where = empty($where) ? "" : " Where " . implode(" And ", $where); |
||||
$sql = substr($sql, 0, -1) . $where; |
||||
if ($return_sql) |
||||
{ |
||||
return $sql; |
||||
} |
||||
else |
||||
{ |
||||
if (self::query($sql)) |
||||
{ |
||||
return mysqli_affected_rows(self::$links[self::$link_name]['conn']); |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static function delete($table = '', $where = null, $return_sql = false) |
||||
{ |
||||
// 小心全部被删除了 |
||||
if (empty($where)) |
||||
{ |
||||
return false; |
||||
} |
||||
$where = 'Where ' . (!is_array($where) ? $where : implode(' And ', $where)); |
||||
$sql = "Delete From `{$table}` {$where}"; |
||||
if ($return_sql) |
||||
{ |
||||
return $sql; |
||||
} |
||||
else |
||||
{ |
||||
if (self::query($sql)) |
||||
{ |
||||
return mysqli_affected_rows(self::$links[self::$link_name]['conn']); |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
public static function ping() |
||||
{ |
||||
if (!mysqli_ping(self::$links[self::$link_name]['conn'])) |
||||
{ |
||||
@mysqli_close(self::$links[self::$link_name]['conn']); |
||||
self::$links[self::$link_name]['conn'] = null; |
||||
self::_init(); |
||||
} |
||||
} |
||||
|
||||
public static function strsafe($array) |
||||
{ |
||||
$arrays = array(); |
||||
if(is_array($array)===true) |
||||
{ |
||||
foreach ($array as $key => $val) |
||||
{ |
||||
if(is_array($val)===true) |
||||
{ |
||||
$arrays[$key] = self::strsafe($val); |
||||
} |
||||
else |
||||
{ |
||||
//先去掉转义,避免下面重复转义了 |
||||
$val = stripslashes($val); |
||||
//进行转义 |
||||
$val = addslashes($val); |
||||
//处理addslashes没法处理的 _ % 字符 |
||||
//$val = strtr($val, array('_'=>'\_', '%'=>'\%')); |
||||
$arrays[$key] = $val; |
||||
} |
||||
} |
||||
return $arrays; |
||||
} |
||||
else |
||||
{ |
||||
$array = stripslashes($array); |
||||
$array = addslashes($array); |
||||
//$array = strtr($array, array('_'=>'\_', '%'=>'\%')); |
||||
return $array; |
||||
} |
||||
} |
||||
|
||||
// 这个是给insert、update、insert_batch、update_batch用的 |
||||
public static function get_fields($table) |
||||
{ |
||||
// $sql = "SHOW COLUMNS FROM $table"; //和下面的语句效果一样 |
||||
$rows = self::get_all("Desc `{$table}`"); |
||||
$fields = array(); |
||||
foreach ($rows as $k => $v) |
||||
{ |
||||
// 过滤自增主键 |
||||
// if ($v['Key'] != 'PRI') |
||||
if ($v['Extra'] != 'auto_increment') |
||||
{ |
||||
$fields[] = $v['Field']; |
||||
} |
||||
} |
||||
return $fields; |
||||
} |
||||
|
||||
public static function table_exists($table_name) |
||||
{ |
||||
$sql = "SHOW TABLES LIKE '" . $table_name . "'"; |
||||
$rsid = self::query($sql); |
||||
$table = self::fetch($rsid); |
||||
if (empty($table)) |
||||
{ |
||||
return false; |
||||
} |
||||
return true; |
||||
} |
||||
} |
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,101 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// PHPSpider公共入口文件 |
||||
//---------------------------------- |
||||
|
||||
// 严格开发模式 |
||||
error_reporting( E_ALL ); |
||||
//ini_set('display_errors', 1); |
||||
|
||||
// 永不超时 |
||||
ini_set('max_execution_time', 0); |
||||
set_time_limit(0); |
||||
// 内存限制,如果外面设置的内存比 /etc/php/php-cli.ini 大,就不要设置了 |
||||
if (intval(ini_get("memory_limit")) < 1024) |
||||
{ |
||||
ini_set('memory_limit', '1024M'); |
||||
} |
||||
|
||||
if( PHP_SAPI != 'cli' ) |
||||
{ |
||||
exit("You must run the CLI environment\n"); |
||||
} |
||||
|
||||
// 设置时区 |
||||
date_default_timezone_set('Asia/Shanghai'); |
||||
|
||||
// 引入PATH_DATA |
||||
require_once __DIR__ . '/constants.php'; |
||||
// 核心库目录 |
||||
define('CORE', dirname(__FILE__)); |
||||
define('PATH_ROOT', CORE."/../"); |
||||
define('PATH_DATA', CORE."/../data"); |
||||
define('PATH_LIBRARY', CORE."/../library"); |
||||
|
||||
// 系统配置 |
||||
if( file_exists( PATH_ROOT."/config/inc_config.php" ) ) |
||||
{ |
||||
require PATH_ROOT."/config/inc_config.php"; |
||||
} |
||||
require CORE.'/log.php'; |
||||
require CORE.'/requests.php'; |
||||
require CORE.'/selector.php'; |
||||
require CORE.'/util.php'; |
||||
require CORE.'/db.php'; |
||||
require CORE.'/cache.php'; |
||||
require CORE."/worker.php"; |
||||
require CORE."/phpspider.php"; |
||||
|
||||
// 启动的时候生成data目录 |
||||
util::path_exists(PATH_DATA); |
||||
util::path_exists(PATH_DATA."/lock"); |
||||
util::path_exists(PATH_DATA."/log"); |
||||
util::path_exists(PATH_DATA."/cache"); |
||||
util::path_exists(PATH_DATA."/status"); |
||||
|
||||
function autoload($classname) { |
||||
set_include_path(PATH_ROOT.'/library/'); |
||||
spl_autoload($classname); //replaces include/require |
||||
} |
||||
|
||||
spl_autoload_extensions('.php'); |
||||
spl_autoload_register('autoload'); |
||||
|
||||
/** |
||||
* 自动加载类库处理 |
||||
* @return void |
||||
*/ |
||||
//function __autoload( $classname ) |
||||
//{ |
||||
//$classname = preg_replace("/[^0-9a-z_]/i", '', $classname); |
||||
//if( class_exists ( $classname ) ) { |
||||
//return true; |
||||
//} |
||||
//$classfile = $classname.'.php'; |
||||
//try |
||||
//{ |
||||
//if ( file_exists ( PATH_LIBRARY.'/'.$classfile ) ) |
||||
//{ |
||||
//require PATH_LIBRARY.'/'.$classfile; |
||||
//} |
||||
//else |
||||
//{ |
||||
//throw new Exception ( 'Error: Cannot find the '.$classname ); |
||||
//} |
||||
//} |
||||
//catch ( Exception $e ) |
||||
//{ |
||||
//log::error($e->getMessage().'|'.$classname); |
||||
//exit(); |
||||
//} |
||||
//} |
@ -0,0 +1,119 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// PHPSpider日志类文件 |
||||
//---------------------------------- |
||||
|
||||
namespace phpspider\core; |
||||
// 引入PATH_DATA |
||||
require_once __DIR__ . '/constants.php'; |
||||
|
||||
class log |
||||
{ |
||||
public static $log_show = false; |
||||
public static $log_type = false; |
||||
public static $log_file = "data/phpspider.log"; |
||||
public static $out_sta = ""; |
||||
public static $out_end = ""; |
||||
|
||||
public static function note($msg) |
||||
{ |
||||
self::$out_sta = self::$out_end = ""; |
||||
self::msg($msg, 'note'); |
||||
} |
||||
|
||||
public static function info($msg) |
||||
{ |
||||
self::$out_sta = self::$out_end = ""; |
||||
self::msg($msg, 'info'); |
||||
} |
||||
|
||||
public static function warn($msg) |
||||
{ |
||||
self::$out_sta = self::$out_end = ""; |
||||
if (!util::is_win()) |
||||
{ |
||||
self::$out_sta = "\033[33m"; |
||||
self::$out_end = "\033[0m"; |
||||
} |
||||
|
||||
self::msg($msg, 'warn'); |
||||
} |
||||
|
||||
public static function debug($msg) |
||||
{ |
||||
self::$out_sta = self::$out_end = ""; |
||||
if (!util::is_win()) |
||||
{ |
||||
self::$out_sta = "\033[36m"; |
||||
self::$out_end = "\033[0m"; |
||||
} |
||||
|
||||
self::msg($msg, 'debug'); |
||||
} |
||||
|
||||
public static function error($msg) |
||||
{ |
||||
self::$out_sta = self::$out_end = ""; |
||||
if (!util::is_win()) |
||||
{ |
||||
self::$out_sta = "\033[31m"; |
||||
self::$out_end = "\033[0m"; |
||||
} |
||||
|
||||
self::msg($msg, 'error'); |
||||
} |
||||
|
||||
public static function msg($msg, $log_type) |
||||
{ |
||||
if ($log_type != 'note' && self::$log_type && strpos(self::$log_type, $log_type) === false) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
if ($log_type == 'note') |
||||
{ |
||||
$msg = self::$out_sta. $msg . "\n".self::$out_end; |
||||
} |
||||
else |
||||
{ |
||||
$msg = self::$out_sta.date("Y-m-d H:i:s")." [{$log_type}] " . $msg .self::$out_end. "\n"; |
||||
} |
||||
if(self::$log_show) |
||||
{ |
||||
echo $msg; |
||||
} |
||||
file_put_contents(self::$log_file, $msg, FILE_APPEND | LOCK_EX); |
||||
} |
||||
|
||||
/** |
||||
* 记录日志 XXX |
||||
* @param string $msg |
||||
* @param string $log_type Note|Warning|Error |
||||
* @return void |
||||
*/ |
||||
public static function add($msg, $log_type = '') |
||||
{ |
||||
if ($log_type != '') |
||||
{ |
||||
$msg = date("Y-m-d H:i:s")." [{$log_type}] " . $msg . "\n"; |
||||
} |
||||
if(self::$log_show) |
||||
{ |
||||
echo $msg; |
||||
} |
||||
//file_put_contents(PATH_DATA."/log/".strtolower($log_type).".log", $msg, FILE_APPEND | LOCK_EX); |
||||
file_put_contents(PATH_DATA."/log/error.log", $msg, FILE_APPEND | LOCK_EX); |
||||
} |
||||
|
||||
} |
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,998 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
// +---------------------------------------------------------------------- |
||||
// | GET请求 |
||||
// | requests::get('http://www.test.com'); |
||||
// | SERVER |
||||
// | $_GET |
||||
// +---------------------------------------------------------------------- |
||||
// | POST请求 |
||||
// | $data = array('name'=>'request'); |
||||
// | requests::post('http://www.test.com', $data); |
||||
// | SERVER |
||||
// | $_POST |
||||
// +---------------------------------------------------------------------- |
||||
// | POST RESTful请求 |
||||
// | $data = array('name'=>'request'); |
||||
// | $data_string = json_encode($data); |
||||
// | requests::set_header("Content-Type", "application/json"); |
||||
// | requests::post('http://www.test.com', $data_string); |
||||
// | SERVER |
||||
// | file_get_contents('php://input') |
||||
// +---------------------------------------------------------------------- |
||||
// | POST 文件上传 |
||||
// | $data = array('file1'=>''./data/phpspider.log''); |
||||
// | requests::post('http://www.test.com', null, $data); |
||||
// | SERVER |
||||
// | $_FILES |
||||
// +---------------------------------------------------------------------- |
||||
// | 代理 |
||||
// | requests::set_proxy(array('223.153.69.150:42354')); |
||||
// | $html = requests::get('https://www.test.com'); |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// PHPSpider请求类文件 |
||||
//---------------------------------- |
||||
|
||||
namespace phpspider\core; |
||||
|
||||
if (!function_exists('curl_file_create')) |
||||
{ |
||||
function curl_file_create($filename, $mimetype = '', $postname = '') |
||||
{ |
||||
return "@$filename;filename=" |
||||
. ($postname ?: basename($filename)) |
||||
. ($mimetype ? ";type=$mimetype" : ''); |
||||
} |
||||
} |
||||
|
||||
class requests |
||||
{ |
||||
const VERSION = '2.0.1'; |
||||
|
||||
protected static $ch = null; |
||||
|
||||
/**** Public variables ****/ |
||||
|
||||
/* user definable vars */ |
||||
|
||||
public static $timeout = 15; |
||||
public static $encoding = null; |
||||
public static $input_encoding = null; |
||||
public static $output_encoding = null; |
||||
public static $cookies = array(); // array of cookies to pass |
||||
// $cookies['username'] = "seatle"; |
||||
public static $rawheaders = array(); // array of raw headers to send |
||||
public static $domain_cookies = array(); // array of cookies for domain to pass |
||||
public static $hosts = array(); // random host binding for make request faster |
||||
public static $headers = array(); // headers returned from server sent here |
||||
public static $useragents = array("requests/2.0.0"); // random agent we masquerade as |
||||
public static $client_ips = array(); // random ip we masquerade as |
||||
public static $proxies = array(); // random proxy ip |
||||
public static $raw = ""; // head + body content returned from server sent here |
||||
public static $head = ""; // head content |
||||
public static $content = ""; // The body before encoding |
||||
public static $text = ""; // The body after encoding |
||||
public static $info = array(); // curl info |
||||
public static $history = 302; // http request status before redirect. ex:30x |
||||
public static $status_code = 0; // http request status |
||||
public static $error = ""; // error messages sent here |
||||
|
||||
/** |
||||
* set timeout |
||||
* $timeout 为数组时会分别设置connect和read |
||||
* |
||||
* @param init or array $timeout |
||||
* @return |
||||
*/ |
||||
public static function set_timeout($timeout) |
||||
{ |
||||
self::$timeout = $timeout; |
||||
} |
||||
|
||||
/** |
||||
* 设置代理 |
||||
* 如果代理有多个,请求时会随机使用 |
||||
* |
||||
* @param mixed $proxies |
||||
* array ( |
||||
* 'socks5://user1:pass2@host:port', |
||||
* 'socks5://user2:pass2@host:port' |
||||
*) |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public static function set_proxy($proxy) |
||||
{ |
||||
self::$proxies = is_array($proxy) ? $proxy : array($proxy); |
||||
} |
||||
|
||||
/** |
||||
* 删除代理 |
||||
* 因为每个链接信息里面都有代理信息,有的链接需要,有的不需要,所以必须提供一个删除功能 |
||||
* |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2018-07-16 17:59 |
||||
*/ |
||||
public static function del_proxy() |
||||
{ |
||||
self::$proxies = array(); |
||||
} |
||||
|
||||
/** |
||||
* 自定义请求头部 |
||||
* 请求头内容可以用 requests::$rawheaders 来获取 |
||||
* 比如获取Content-Type:requests::$rawheaders['Content-Type'] |
||||
* |
||||
* @param string $headers |
||||
* @return void |
||||
*/ |
||||
public static function set_header($key, $value) |
||||
{ |
||||
self::$rawheaders[$key] = $value; |
||||
} |
||||
|
||||
/** |
||||
* 设置全局COOKIE |
||||
* |
||||
* @param string $cookie |
||||
* @return void |
||||
*/ |
||||
public static function set_cookie($key, $value, $domain = '') |
||||
{ |
||||
if (empty($key)) |
||||
{ |
||||
return false; |
||||
} |
||||
if (!empty($domain)) |
||||
{ |
||||
self::$domain_cookies[$domain][$key] = $value; |
||||
} |
||||
else |
||||
{ |
||||
self::$cookies[$key] = $value; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 批量设置全局cookie |
||||
* |
||||
* @param mixed $cookies |
||||
* @param string $domain |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function set_cookies($cookies, $domain = '') |
||||
{ |
||||
$cookies_arr = explode(';', $cookies); |
||||
if (empty($cookies_arr)) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
foreach ($cookies_arr as $cookie) |
||||
{ |
||||
$cookie_arr = explode('=', $cookie, 2); |
||||
$key = $cookie_arr[0]; |
||||
$value = empty($cookie_arr[1]) ? '' : $cookie_arr[1]; |
||||
|
||||
if (!empty($domain)) |
||||
{ |
||||
self::$domain_cookies[$domain][$key] = $value; |
||||
} |
||||
else |
||||
{ |
||||
self::$cookies[$key] = $value; |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 获取单一Cookie |
||||
* |
||||
* @param mixed $name cookie名称 |
||||
* @param string $domain 不传则取全局cookie,就是手动set_cookie的cookie |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function get_cookie($name, $domain = '') |
||||
{ |
||||
if (!empty($domain) && !isset(self::$domain_cookies[$domain])) |
||||
{ |
||||
return ''; |
||||
} |
||||
$cookies = empty($domain) ? self::$cookies : self::$domain_cookies[$domain]; |
||||
return isset($cookies[$name]) ? $cookies[$name] : ''; |
||||
} |
||||
|
||||
/** |
||||
* 获取Cookie数组 |
||||
* |
||||
* @param string $domain 不传则取全局cookie,就是手动set_cookie的cookie |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function get_cookies($domain = '') |
||||
{ |
||||
if (!empty($domain) && !isset(self::$domain_cookies[$domain])) |
||||
{ |
||||
return array(); |
||||
} |
||||
return empty($domain) ? self::$cookies : self::$domain_cookies[$domain]; |
||||
} |
||||
|
||||
/** |
||||
* 删除Cookie |
||||
* |
||||
* @param string $domain 不传则删除全局Cookie |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function del_cookie($key, $domain = '') |
||||
{ |
||||
if (empty($key)) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
if (!empty($domain) && !isset(self::$domain_cookies[$domain])) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
if (!empty($domain)) |
||||
{ |
||||
if (isset(self::$domain_cookies[$domain][$key])) |
||||
{ |
||||
unset(self::$domain_cookies[$domain][$key]); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
if (isset(self::$cookies[$key])) |
||||
{ |
||||
unset(self::$cookies[$key]); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 删除Cookie |
||||
* |
||||
* @param string $domain 不传则删除全局Cookie |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function del_cookies($domain = '') |
||||
{ |
||||
if (!empty($domain) && !isset(self::$domain_cookies[$domain])) |
||||
{ |
||||
return false; |
||||
} |
||||
if ( empty($domain) ) |
||||
{ |
||||
self::$cookies = array(); |
||||
} |
||||
else |
||||
{ |
||||
if (isset(self::$domain_cookies[$domain])) |
||||
{ |
||||
unset(self::$domain_cookies[$domain]); |
||||
} |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* 设置随机的user_agent |
||||
* |
||||
* @param string $useragent |
||||
* @return void |
||||
*/ |
||||
public static function set_useragent($useragent) |
||||
{ |
||||
self::$useragents = is_array($useragent) ? $useragent : array($useragent); |
||||
} |
||||
|
||||
/** |
||||
* set referer |
||||
* |
||||
*/ |
||||
public static function set_referer($referer) |
||||
{ |
||||
self::$rawheaders['Referer'] = $referer; |
||||
} |
||||
|
||||
/** |
||||
* 设置伪造IP |
||||
* 传入数组则为随机IP |
||||
* @param string $ip |
||||
* @return void |
||||
*/ |
||||
public static function set_client_ip($ip) |
||||
{ |
||||
self::$client_ips = is_array($ip) ? $ip : array($ip); |
||||
} |
||||
|
||||
/** |
||||
* 删除伪造IP |
||||
* |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2018-07-16 17:59 |
||||
*/ |
||||
public static function del_client_ip() |
||||
{ |
||||
self::$client_ips = array(); |
||||
} |
||||
|
||||
/** |
||||
* 设置中文请求 |
||||
* |
||||
* @param string $lang |
||||
* @return void |
||||
*/ |
||||
public static function set_accept_language($lang = 'zh-CN') |
||||
{ |
||||
self::$rawheaders['Accept-Language'] = $lang; |
||||
} |
||||
|
||||
/** |
||||
* 设置Hosts |
||||
* 负载均衡到不同的服务器,如果对方使用CDN,采用这个是最好的了 |
||||
* |
||||
* @param string $hosts |
||||
* @return void |
||||
*/ |
||||
public static function set_hosts($host, $ips = array()) |
||||
{ |
||||
$ips = is_array($ips) ? $ips : array($ips); |
||||
self::$hosts[$host] = $ips; |
||||
} |
||||
|
||||
/** |
||||
* 分割返回的header和body |
||||
* header用来判断编码和获取Cookie |
||||
* body用来判断编码,得到编码前和编码后的内容 |
||||
* |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function split_header_body() |
||||
{ |
||||
$head = $body = ''; |
||||
$head = substr(self::$raw, 0, self::$info['header_size']); |
||||
$body = substr(self::$raw, self::$info['header_size']); |
||||
// http header |
||||
self::$head = $head; |
||||
// The body before encoding |
||||
self::$content = $body; |
||||
|
||||
//$http_headers = array(); |
||||
//// 解析HTTP数据流 |
||||
//if (!empty(self::$raw)) |
||||
//{ |
||||
//self::get_response_cookies($domain); |
||||
//// body里面可能有 \r\n\r\n,但是第一个一定是HTTP Header,去掉后剩下的就是body |
||||
//$array = explode("\r\n\r\n", self::$raw); |
||||
//foreach ($array as $k=>$v) |
||||
//{ |
||||
//// post 方法会有两个http header:HTTP/1.1 100 Continue、HTTP/1.1 200 OK |
||||
//if (preg_match("#^HTTP/.*? 100 Continue#", $v)) |
||||
//{ |
||||
//unset($array[$k]); |
||||
//continue; |
||||
//} |
||||
//if (preg_match("#^HTTP/.*? \d+ #", $v)) |
||||
//{ |
||||
//$header = $v; |
||||
//unset($array[$k]); |
||||
//$http_headers = self::get_response_headers($v); |
||||
//} |
||||
//} |
||||
//$body = implode("\r\n\r\n", $array); |
||||
//} |
||||
|
||||
// 设置了输出编码的转码,注意: xpath只支持utf-8,iso-8859-1 不要转,他本身就是utf-8 |
||||
$body = self::encoding($body); //自动转码 |
||||
// 转码后 |
||||
self::$encoding = self::$output_encoding; |
||||
|
||||
// The body after encoding |
||||
self::$text = $body; |
||||
return array($head, $body); |
||||
} |
||||
|
||||
/** |
||||
* 获得域名相对应的Cookie |
||||
* |
||||
* @param mixed $header |
||||
* @param mixed $domain |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function get_response_cookies($header, $domain) |
||||
{ |
||||
// 解析Cookie并存入 self::$cookies 方便调用 |
||||
preg_match_all("/.*?Set\-Cookie: ([^\r\n]*)/i", $header, $matches); |
||||
$cookies = empty($matches[1]) ? array() : $matches[1]; |
||||
|
||||
// 解析到Cookie |
||||
if (!empty($cookies)) |
||||
{ |
||||
$cookies = implode(';', $cookies); |
||||
$cookies = explode(';', $cookies); |
||||
foreach ($cookies as $cookie) |
||||
{ |
||||
$cookie_arr = explode('=', $cookie, 2); |
||||
// 过滤 httponly、secure |
||||
if (count($cookie_arr) < 2) |
||||
{ |
||||
continue; |
||||
} |
||||
$cookie_name = !empty($cookie_arr[0]) ? trim($cookie_arr[0]) : ''; |
||||
if (empty($cookie_name)) |
||||
{ |
||||
continue; |
||||
} |
||||
// 过滤掉domain路径 |
||||
if (in_array(strtolower($cookie_name), array('path', 'domain', 'expires', 'max-age'))) |
||||
{ |
||||
continue; |
||||
} |
||||
self::$domain_cookies[$domain][trim($cookie_arr[0])] = trim($cookie_arr[1]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获得response header |
||||
* 此方法占时没有用到 |
||||
* |
||||
* @param mixed $header |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function get_response_headers($header) |
||||
{ |
||||
$headers = array(); |
||||
$header_lines = explode("\n", $header); |
||||
if (!empty($header_lines)) |
||||
{ |
||||
foreach ($header_lines as $line) |
||||
{ |
||||
$header_arr = explode(':', $line, 2); |
||||
$key = empty($header_arr[0]) ? '' : trim($header_arr[0]); |
||||
$val = empty($header_arr[1]) ? '' : trim($header_arr[1]); |
||||
if (empty($key) || empty($val)) |
||||
{ |
||||
continue; |
||||
} |
||||
$headers[$key] = $val; |
||||
} |
||||
} |
||||
self::$headers = $headers; |
||||
return self::$headers; |
||||
} |
||||
|
||||
/** |
||||
* 获取编码 |
||||
* @param $string |
||||
* @return string |
||||
*/ |
||||
public static function get_encoding($string) |
||||
{ |
||||
$encoding = mb_detect_encoding($string, array('UTF-8', 'GBK', 'GB2312', 'LATIN1', 'ASCII', 'BIG5', 'ISO-8859-1')); |
||||
return strtolower($encoding); |
||||
} |
||||
|
||||
/** |
||||
* 移除页面head区域代码 |
||||
* @param $html |
||||
* @return mixed |
||||
*/ |
||||
private static function _remove_head($html) |
||||
{ |
||||
return preg_replace('/<head.+?>.+<\/head>/is', '<head></head>', $html); |
||||
} |
||||
|
||||
/** |
||||
* 简单的判断一下参数是否为一个URL链接 |
||||
* @param string $str |
||||
* @return boolean |
||||
*/ |
||||
private static function _is_url($url) |
||||
{ |
||||
//$pattern = '/^http(s)?:\\/\\/.+/'; |
||||
$pattern = "/\b(([\w-]+:\/\/?|www[.])[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|\/)))/"; |
||||
if (preg_match($pattern, $url)) |
||||
{ |
||||
return true; |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 初始化 CURL |
||||
* |
||||
*/ |
||||
public static function init() |
||||
{ |
||||
if (!is_resource ( self::$ch )) |
||||
{ |
||||
self::$ch = curl_init (); |
||||
curl_setopt( self::$ch, CURLOPT_RETURNTRANSFER, true ); |
||||
curl_setopt( self::$ch, CURLOPT_HEADER, false ); |
||||
curl_setopt( self::$ch, CURLOPT_USERAGENT, "phpspider-requests/".self::VERSION ); |
||||
// 如果设置了两个时间,就分开设置 |
||||
if (is_array(self::$timeout)) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_CONNECTTIMEOUT, self::$timeout[0] ); |
||||
curl_setopt( self::$ch, CURLOPT_TIMEOUT, self::$timeout[1]); |
||||
} |
||||
else |
||||
{ |
||||
curl_setopt(self::$ch, CURLOPT_CONNECTTIMEOUT, ceil(self::$timeout / 2)); |
||||
curl_setopt(self::$ch, CURLOPT_TIMEOUT, self::$timeout); |
||||
} |
||||
curl_setopt(self::$ch, CURLOPT_MAXREDIRS, 5); //maximum number of redirects allowed |
||||
// 在多线程处理场景下使用超时选项时,会忽略signals对应的处理函数,但是无耐的是还有小概率的crash情况发生 |
||||
curl_setopt( self::$ch, CURLOPT_NOSIGNAL, true); |
||||
} |
||||
return self::$ch; |
||||
} |
||||
|
||||
/** |
||||
* get 请求 |
||||
*/ |
||||
public static function get($url, $fields = array(), $allow_redirects = true, $cert = NULL) |
||||
{ |
||||
self::init (); |
||||
return self::request($url, 'get', $fields, NULL, $allow_redirects, $cert); |
||||
} |
||||
|
||||
/** |
||||
* post 请求 |
||||
* $fields 有三种类型:1、数组;2、http query;3、json |
||||
* 1、array('name'=>'yangzetao') |
||||
* 2、http_build_query(array('name'=>'yangzetao')) |
||||
* 3、json_encode(array('name'=>'yangzetao')) |
||||
* 前两种是普通的post,可以用$_POST方式获取 |
||||
* 第三种是post stream( json rpc,其实就是webservice ) |
||||
* 虽然是post方式,但是只能用流方式 http://input 后者 $HTTP_RAW_POST_DATA 获取 |
||||
* |
||||
* @param mixed $url |
||||
* @param array $fields |
||||
* @param mixed $proxies |
||||
* @static |
||||
* @access public |
||||
* @return void |
||||
*/ |
||||
public static function post($url, $fields = array(), $files = array(), $allow_redirects = true, $cert = NULL) |
||||
{ |
||||
self::init (); |
||||
return self::request($url, 'POST', $fields, $files, $allow_redirects, $cert); |
||||
} |
||||
|
||||
public static function put($url, $fields = array(), $allow_redirects = true, $cert = NULL) |
||||
{ |
||||
self::init (); |
||||
return self::request($url, 'PUT', $fields, $allow_redirects, $cert); |
||||
} |
||||
|
||||
public static function delete($url, $fields = array(), $allow_redirects = true, $cert = NULL) |
||||
{ |
||||
self::init (); |
||||
return self::request($url, 'DELETE', $fields, $allow_redirects, $cert); |
||||
} |
||||
|
||||
// 响应HTTP头域里的元信息 |
||||
// 此方法被用来获取请求实体的元信息而不需要传输实体主体(entity-body) |
||||
// 此方法经常被用来测试超文本链接的有效性,可访问性,和最近的改变。. |
||||
public static function head($url, $fields = array(), $allow_redirects = true, $cert = NULL) |
||||
{ |
||||
self::init (); |
||||
self::request($url, 'HEAD', $fields, $allow_redirects, $cert); |
||||
} |
||||
|
||||
public static function options($url, $fields = array(), $allow_redirects = true, $cert = NULL) |
||||
{ |
||||
self::init (); |
||||
return self::request($url, 'OPTIONS', $fields, $allow_redirects, $cert); |
||||
} |
||||
|
||||
public static function patch($url, $fields = array(), $allow_redirects = true, $cert = NULL) |
||||
{ |
||||
self::init (); |
||||
return self::request($url, 'PATCH', $fields, $allow_redirects, $cert); |
||||
} |
||||
|
||||
/** |
||||
* request |
||||
* |
||||
* @param mixed $url 请求URL |
||||
* @param string $method 请求方法 |
||||
* @param array $fields 表单字段 |
||||
* @param array $files 上传文件 |
||||
* @param mixed $cert CA证书 |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function request($url, $method = 'GET', $fields = array(), $files = array(), $allow_redirects = true, $cert = NULL) |
||||
{ |
||||
$method = strtoupper($method); |
||||
if(!self::_is_url($url)) |
||||
{ |
||||
self::$error = "You have requested URL ({$url}) is not a valid HTTP address"; |
||||
return false; |
||||
} |
||||
|
||||
// 如果是 get 方式,直接拼凑一个 url 出来 |
||||
if ($method == 'GET' && !empty($fields)) |
||||
{ |
||||
$url = $url.(strpos($url, '?') === false ? '?' : '&').http_build_query($fields); |
||||
} |
||||
|
||||
$parse_url = parse_url($url); |
||||
if (empty($parse_url) || empty($parse_url['host']) || !in_array($parse_url['scheme'], array('http', 'https'))) |
||||
{ |
||||
self::$error = "No connection adapters were found for '{$url}'"; |
||||
return false; |
||||
} |
||||
$scheme = $parse_url['scheme']; |
||||
$domain = $parse_url['host']; |
||||
|
||||
// 随机绑定 hosts,做负载均衡 |
||||
if (self::$hosts) |
||||
{ |
||||
if (isset(self::$hosts[$domain])) |
||||
{ |
||||
$hosts = self::$hosts[$domain]; |
||||
$key = rand(0, count($hosts)-1); |
||||
$ip = $hosts[$key]; |
||||
$url = str_replace($domain, $ip, $url); |
||||
self::$rawheaders['Host'] = $domain; |
||||
} |
||||
} |
||||
|
||||
curl_setopt( self::$ch, CURLOPT_URL, $url ); |
||||
|
||||
if ($method != 'GET') |
||||
{ |
||||
// 如果是 post 方式 |
||||
if ($method == 'POST') |
||||
{ |
||||
//curl_setopt( self::$ch, CURLOPT_POST, true ); |
||||
$tmpheaders = array_change_key_case(self::$rawheaders, CASE_LOWER); |
||||
// 有些RESTful服务只接受JSON形态的数据 |
||||
// CURLOPT_POST会把上傳的文件类型设为 multipart/form-data |
||||
// 把CURLOPT_POSTFIELDS的内容按multipart/form-data 的形式编码 |
||||
// CURLOPT_CUSTOMREQUEST可以按指定内容上传 |
||||
if ( isset($tmpheaders['content-type']) && $tmpheaders['content-type'] == 'application/json' ) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_CUSTOMREQUEST, $method ); |
||||
} |
||||
else |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_POST, true ); |
||||
} |
||||
|
||||
$file_fields = array(); |
||||
if (!empty($files)) |
||||
{ |
||||
foreach ($files as $postname => $file) |
||||
{ |
||||
$filepath = realpath($file); |
||||
// 如果文件不存在 |
||||
if (!file_exists($filepath)) |
||||
{ |
||||
continue; |
||||
} |
||||
|
||||
$filename = basename($filepath); |
||||
$type = self::get_mimetype($filepath); |
||||
$file_fields[$postname] = curl_file_create($filepath, $type, $filename); |
||||
// curl -F "name=seatle&file=@/absolute/path/to/image.png" htt://localhost/uploadfile.php |
||||
//$cfile = '@'.realpath($filename).";type=".$type.";filename=".$filename; |
||||
} |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
self::$rawheaders['X-HTTP-Method-Override'] = $method; |
||||
curl_setopt( self::$ch, CURLOPT_CUSTOMREQUEST, $method ); |
||||
} |
||||
|
||||
if ( $method == 'POST' ) |
||||
{ |
||||
// 不是上传文件的,用http_build_query, 能实现更好的兼容性,更小的请求数据包 |
||||
if ( empty($file_fields) ) |
||||
{ |
||||
// post方式 |
||||
if ( is_array($fields) ) |
||||
{ |
||||
$fields = http_build_query($fields); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
// 有post数据 |
||||
if ( is_array($fields) && !empty($fields) ) |
||||
{ |
||||
// 某些server可能会有问题 |
||||
$fields = array_merge($fields, $file_fields); |
||||
} |
||||
else |
||||
{ |
||||
$fields = $file_fields; |
||||
} |
||||
} |
||||
|
||||
// 不能直接传数组,不知道是什么Bug,会非常慢 |
||||
curl_setopt( self::$ch, CURLOPT_POSTFIELDS, $fields ); |
||||
} |
||||
} |
||||
|
||||
$cookies = self::get_cookies(); |
||||
$domain_cookies = self::get_cookies($domain); |
||||
$cookies = array_merge($cookies, $domain_cookies); |
||||
// 是否设置了cookie |
||||
if (!empty($cookies)) |
||||
{ |
||||
foreach ($cookies as $key=>$value) |
||||
{ |
||||
$cookie_arr[] = $key.'='.$value; |
||||
} |
||||
$cookies = implode('; ', $cookie_arr); |
||||
curl_setopt(self::$ch, CURLOPT_COOKIE, $cookies); |
||||
} |
||||
|
||||
if (!empty(self::$useragents)) |
||||
{ |
||||
$key = rand(0, count(self::$useragents) - 1); |
||||
self::$rawheaders['User-Agent'] = self::$useragents[$key]; |
||||
} |
||||
|
||||
if (!empty(self::$client_ips)) |
||||
{ |
||||
$key = rand(0, count(self::$client_ips) - 1); |
||||
self::$rawheaders['CLIENT-IP'] = self::$client_ips[$key]; |
||||
self::$rawheaders['X-FORWARDED-FOR'] = self::$client_ips[$key]; |
||||
} |
||||
|
||||
if (self::$rawheaders) |
||||
{ |
||||
$http_headers = array(); |
||||
foreach (self::$rawheaders as $k=>$v) |
||||
{ |
||||
$http_headers[] = $k.': '.$v; |
||||
} |
||||
curl_setopt( self::$ch, CURLOPT_HTTPHEADER, $http_headers ); |
||||
} |
||||
|
||||
curl_setopt( self::$ch, CURLOPT_ENCODING, 'gzip' ); |
||||
|
||||
// 关闭验证 |
||||
if ($scheme == 'https') |
||||
{ |
||||
curl_setopt(self::$ch, CURLOPT_SSL_VERIFYPEER, false); |
||||
curl_setopt(self::$ch, CURLOPT_SSL_VERIFYHOST, false); |
||||
} |
||||
|
||||
if (self::$proxies) |
||||
{ |
||||
$key = rand(0, count(self::$proxies) - 1); |
||||
$proxy = self::$proxies[$key]; |
||||
curl_setopt( self::$ch, CURLOPT_PROXY, $proxy ); |
||||
} |
||||
|
||||
// header + body,header 里面有 cookie |
||||
curl_setopt( self::$ch, CURLOPT_HEADER, true ); |
||||
// 请求跳转后的内容 |
||||
if ($allow_redirects) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_FOLLOWLOCATION, true); |
||||
} |
||||
|
||||
self::$raw = curl_exec ( self::$ch ); |
||||
// 真实url |
||||
//$location = curl_getinfo( self::$ch, CURLINFO_EFFECTIVE_URL); |
||||
self::$info = curl_getinfo( self::$ch ); |
||||
//print_r(self::$info); |
||||
self::$status_code = self::$info['http_code']; |
||||
if (self::$raw === false) |
||||
{ |
||||
self::$error = 'Curl error: ' . curl_error( self::$ch ); |
||||
//trigger_error(self::$error, E_USER_WARNING); |
||||
} |
||||
|
||||
// 关闭句柄 |
||||
curl_close( self::$ch ); |
||||
|
||||
// 请求成功之后才把URL存起来 |
||||
list($header, $text) = self::split_header_body(); |
||||
self::$history = self::get_history($header); |
||||
self::$headers = self::get_response_headers($header); |
||||
self::get_response_cookies($header, $domain); |
||||
//$data = substr($data, 10); |
||||
//$data = gzinflate($data); |
||||
return $text; |
||||
} |
||||
|
||||
public static function get_history($header) |
||||
{ |
||||
$status_code = 0; |
||||
$lines = explode("\n", $header); |
||||
foreach ($lines as $line) |
||||
{ |
||||
$line = trim($line); |
||||
if (preg_match("#^HTTP/.*? (\d+) Found#", $line, $out)) |
||||
{ |
||||
$status_code = empty($out[1]) ? 0 : intval($out[1]); |
||||
} |
||||
} |
||||
return $status_code; |
||||
} |
||||
|
||||
// 获取 mimetype |
||||
public static function get_mimetype($filepath) |
||||
{ |
||||
$fp = finfo_open(FILEINFO_MIME); |
||||
$mime = finfo_file($fp, $filepath); |
||||
finfo_close($fp); |
||||
$arr = explode(';', $mime); |
||||
$type = empty($arr[0]) ? '' : $arr[0]; |
||||
return $type; |
||||
} |
||||
|
||||
/** |
||||
* 拼凑文件和表单 |
||||
* 占时没有用到 |
||||
* |
||||
* @param mixed $post_fields |
||||
* @param mixed $file_fields |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2017-08-03 18:06 |
||||
*/ |
||||
public static function get_postfile_form($post_fields, $file_fields) |
||||
{ |
||||
// 构造post数据 |
||||
$data = ''; |
||||
$delimiter = '-------------' . uniqid(); |
||||
// 表单数据 |
||||
foreach ($post_fields as $name => $content) |
||||
{ |
||||
$data .= '--'.$delimiter."\r\n"; |
||||
$data .= 'Content-Disposition: form-data; name = "'.$name.'"'; |
||||
$data .= "\r\n\r\n"; |
||||
$data .= $content; |
||||
$data .= "\r\n"; |
||||
} |
||||
|
||||
foreach ($file_fields as $input_name => $file) |
||||
{ |
||||
$data .= '--'.$delimiter."\r\n"; |
||||
$data .= 'Content-Disposition: form-data; name = "'.$input_name.'";'. |
||||
' filename="'.$file['filename'].'"'."\r\n"; |
||||
$data .= "Content-Type: {$file['type']}\r\n"; |
||||
$data .= "\r\n"; |
||||
$data .= $file['content']; |
||||
$data .= "\r\n"; |
||||
} |
||||
|
||||
// 结束符 |
||||
$data .= '--'.$delimiter."--\r\n"; |
||||
|
||||
//return array( |
||||
//CURLOPT_HTTPHEADER => array( |
||||
//'Content-Type:multipart/form-data;boundary=' . $delimiter, |
||||
//'Content-Length:' . strlen($data) |
||||
//), |
||||
//CURLOPT_POST => true, |
||||
//CURLOPT_POSTFIELDS => $data, |
||||
//); |
||||
return array($delimiter, $data); |
||||
} |
||||
|
||||
/** |
||||
* html encoding transform |
||||
* |
||||
* @param string $html |
||||
* @param string $in |
||||
* @param string $out |
||||
* @param string $content |
||||
* @param string $mode |
||||
* auto|iconv|mb_convert_encoding |
||||
* @return string |
||||
*/ |
||||
public static function encoding($html, $in = null, $out = null, $mode = 'auto') |
||||
{ |
||||
$valid = array( |
||||
'auto', |
||||
'iconv', |
||||
'mb_convert_encoding', |
||||
); |
||||
if (isset(self::$output_encoding)) |
||||
{ |
||||
$out = self::$output_encoding; |
||||
} |
||||
if ( ! isset($out)) |
||||
{ |
||||
$out = 'UTF-8'; |
||||
} |
||||
if ( ! in_array($mode, $valid)) |
||||
{ |
||||
throw new Exception('invalid mode, mode='.$mode); |
||||
} |
||||
$if = function_exists('mb_convert_encoding'); |
||||
$if = $if && ($mode == 'auto' || $mode == 'mb_convert_encoding'); |
||||
if (function_exists('iconv') && ($mode == 'auto' || $mode == 'iconv')) |
||||
{ |
||||
$func = 'iconv'; |
||||
} |
||||
elseif ($if) |
||||
{ |
||||
$func = 'mb_convert_encoding'; |
||||
} |
||||
else |
||||
{ |
||||
throw new Exception('charsetTrans failed, no function'); |
||||
} |
||||
|
||||
$pattern = '/(<meta[^>]*?charset=([\"\']?))([a-z\d_\-]*)(\2[^>]*?>)/is'; |
||||
if ( ! isset($in)) |
||||
{ |
||||
$n = preg_match($pattern, $html, $in); |
||||
if ($n > 0) |
||||
{ |
||||
$in = $in[3]; |
||||
} |
||||
else |
||||
{ |
||||
$in = null; |
||||
} |
||||
if (empty($in) and function_exists('mb_detect_encoding')) |
||||
{ |
||||
$in = mb_detect_encoding($html, array('UTF-8', 'GBK', 'GB2312', 'LATIN1', 'ASCII', 'BIG5', 'ISO-8859-1')); |
||||
} |
||||
} |
||||
|
||||
if (isset($in)) |
||||
{ |
||||
if ($in == 'ISO-8859-1') |
||||
{ |
||||
$in = 'UTF-8'; |
||||
} |
||||
$old = error_reporting(error_reporting() & ~E_NOTICE); |
||||
$html = call_user_func($func, $in, $out.'//IGNORE', $html); |
||||
error_reporting($old); |
||||
$html = preg_replace($pattern, "\\1$out\\4", $html, 1); |
||||
} |
||||
return $html; |
||||
} |
||||
} |
@ -0,0 +1,588 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// PHPSpider选择器类文件 |
||||
//---------------------------------- |
||||
|
||||
namespace phpspider\core; |
||||
|
||||
use phpspider\library\phpquery; |
||||
use DOMDocument; |
||||
use DOMXpath; |
||||
use Exception; |
||||
|
||||
class selector |
||||
{ |
||||
/** |
||||
* 版本号 |
||||
* @var string |
||||
*/ |
||||
const VERSION = '1.0.2'; |
||||
public static $dom = null; |
||||
public static $dom_auth = ''; |
||||
public static $xpath = null; |
||||
public static $error = null; |
||||
|
||||
public static function select($html, $selector, $selector_type = 'xpath') |
||||
{ |
||||
if (empty($html) || empty($selector)) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
$selector_type = strtolower($selector_type); |
||||
if ($selector_type == 'xpath') |
||||
{ |
||||
return self::_xpath_select($html, $selector); |
||||
} |
||||
elseif ($selector_type == 'regex') |
||||
{ |
||||
return self::_regex_select($html, $selector); |
||||
} |
||||
elseif ($selector_type == 'css') |
||||
{ |
||||
return self::_css_select($html, $selector); |
||||
} |
||||
} |
||||
|
||||
public static function remove($html, $selector, $selector_type = 'xpath') |
||||
{ |
||||
if (empty($html) || empty($selector)) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
$remove_html = ""; |
||||
$selector_type = strtolower($selector_type); |
||||
if ($selector_type == 'xpath') |
||||
{ |
||||
$remove_html = self::_xpath_select($html, $selector, true); |
||||
} |
||||
elseif ($selector_type == 'regex') |
||||
{ |
||||
$remove_html = self::_regex_select($html, $selector, true); |
||||
} |
||||
elseif ($selector_type == 'css') |
||||
{ |
||||
$remove_html = self::_css_select($html, $selector, true); |
||||
} |
||||
$html = str_replace($remove_html, "", $html); |
||||
return $html; |
||||
} |
||||
|
||||
/** |
||||
* xpath选择器 |
||||
* |
||||
* @param mixed $html |
||||
* @param mixed $selector |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-10-26 12:53 |
||||
*/ |
||||
private static function _xpath_select($html, $selector, $remove = false) |
||||
{ |
||||
if (!is_object(self::$dom)) |
||||
{ |
||||
self::$dom = new DOMDocument(); |
||||
} |
||||
|
||||
// 如果加载的不是之前的HTML内容,替换一下验证标识 |
||||
if (self::$dom_auth != md5($html)) |
||||
{ |
||||
self::$dom_auth = md5($html); |
||||
@self::$dom->loadHTML('<?xml encoding="UTF-8">'.$html); |
||||
self::$xpath = new DOMXpath(self::$dom); |
||||
} |
||||
|
||||
//libxml_use_internal_errors(true); |
||||
//self::$dom->loadHTML('<?xml encoding="UTF-8">'.$html); |
||||
//$errors = libxml_get_errors(); |
||||
//if (!empty($errors)) |
||||
//{ |
||||
//print_r($errors); |
||||
//exit; |
||||
//} |
||||
|
||||
$elements = @self::$xpath->query($selector); |
||||
if ($elements === false) |
||||
{ |
||||
self::$error = "the selector in the xpath(\"{$selector}\") syntax errors"; |
||||
// 不应该返回false,因为isset(false)为true,更不能通过 !$values 去判断,因为!0为true,所以这里只能返回null |
||||
//return false; |
||||
return null; |
||||
} |
||||
|
||||
$result = array(); |
||||
if (!is_null($elements)) |
||||
{ |
||||
foreach ($elements as $element) |
||||
{ |
||||
// 如果是删除操作,取一整块代码 |
||||
if ($remove) |
||||
{ |
||||
$content = self::$dom->saveXml($element); |
||||
} |
||||
else |
||||
{ |
||||
$nodeName = $element->nodeName; |
||||
$nodeType = $element->nodeType; // 1.Element 2.Attribute 3.Text |
||||
//$nodeAttr = $element->getAttribute('src'); |
||||
//$nodes = util::node_to_array(self::$dom, $element); |
||||
//echo $nodes['@src']."\n"; |
||||
// 如果是img标签,直接取src值 |
||||
if ($nodeType == 1 && in_array($nodeName, array('img'))) |
||||
{ |
||||
$content = $element->getAttribute('src'); |
||||
} |
||||
// 如果是标签属性,直接取节点值 |
||||
elseif ($nodeType == 2 || $nodeType == 3 || $nodeType == 4) |
||||
{ |
||||
$content = $element->nodeValue; |
||||
} |
||||
else |
||||
{ |
||||
// 保留nodeValue里的html符号,给children二次提取 |
||||
$content = self::$dom->saveXml($element); |
||||
//$content = trim(self::$dom->saveHtml($element)); |
||||
$content = preg_replace(array("#^<{$nodeName}.*>#isU","#</{$nodeName}>$#isU"), array('', ''), $content); |
||||
} |
||||
} |
||||
$result[] = $content; |
||||
} |
||||
} |
||||
if (empty($result)) |
||||
{ |
||||
return null; |
||||
} |
||||
// 如果只有一个元素就直接返回string,否则返回数组 |
||||
return count($result) > 1 ? $result : $result[0]; |
||||
} |
||||
|
||||
/** |
||||
* css选择器 |
||||
* |
||||
* @param mixed $html |
||||
* @param mixed $selector |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-10-26 12:53 |
||||
*/ |
||||
private static function _css_select($html, $selector, $remove = false) |
||||
{ |
||||
$selector = self::css_to_xpath($selector); |
||||
//echo $selector."\n"; |
||||
//exit("\n"); |
||||
return self::_xpath_select($html, $selector, $remove); |
||||
// 如果加载的不是之前的HTML内容,替换一下验证标识 |
||||
//if (self::$dom_auth['css'] != md5($html)) |
||||
//{ |
||||
//self::$dom_auth['css'] = md5($html); |
||||
//phpQuery::loadDocumentHTML($html); |
||||
//} |
||||
//if ($remove) |
||||
//{ |
||||
//return phpQuery::pq($selector)->remove(); |
||||
//} |
||||
//else |
||||
//{ |
||||
//return phpQuery::pq($selector)->html(); |
||||
//} |
||||
} |
||||
|
||||
/** |
||||
* 正则选择器 |
||||
* |
||||
* @param mixed $html |
||||
* @param mixed $selector |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-10-26 12:53 |
||||
*/ |
||||
private static function _regex_select($html, $selector, $remove = false) |
||||
{ |
||||
if(@preg_match_all($selector, $html, $out) === false) |
||||
{ |
||||
self::$error = "the selector in the regex(\"{$selector}\") syntax errors"; |
||||
return null; |
||||
} |
||||
$count = count($out); |
||||
$result = array(); |
||||
// 一个都没有匹配到 |
||||
if ($count == 0) |
||||
{ |
||||
return null; |
||||
} |
||||
// 只匹配一个,就是只有一个 () |
||||
elseif ($count == 2) |
||||
{ |
||||
// 删除的话取匹配到的所有内容 |
||||
if ($remove) |
||||
{ |
||||
$result = $out[0]; |
||||
} |
||||
else |
||||
{ |
||||
$result = $out[1]; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
for ($i = 1; $i < $count; $i++) |
||||
{ |
||||
// 如果只有一个元素,就直接返回好了 |
||||
$result[] = count($out[$i]) > 1 ? $out[$i] : $out[$i][0]; |
||||
} |
||||
} |
||||
if (empty($result)) |
||||
{ |
||||
return null; |
||||
} |
||||
|
||||
return count($result) > 1 ? $result : $result[0]; |
||||
} |
||||
|
||||
public static function find_all($html, $selector) |
||||
{ |
||||
} |
||||
|
||||
|
||||
public static function css_to_xpath($selectors) |
||||
{ |
||||
$queries = self::parse_selector($selectors); |
||||
$delimiter_before = false; |
||||
$xquery = ''; |
||||
foreach($queries as $s) |
||||
{ |
||||
// TAG |
||||
$is_tag = preg_match('@^[\w|\||-]+$@', $s) || $s == '*'; |
||||
if ($is_tag) |
||||
{ |
||||
$xquery .= $s; |
||||
} |
||||
// ID |
||||
else if ($s[0] == '#') |
||||
{ |
||||
if ($delimiter_before) |
||||
{ |
||||
$xquery .= '*'; |
||||
} |
||||
// ID用精确查询 |
||||
$xquery .= "[@id='".substr($s, 1)."']"; |
||||
} |
||||
// CLASSES |
||||
else if ($s[0] == '.') |
||||
{ |
||||
if ($delimiter_before) |
||||
{ |
||||
$xquery .= '*'; |
||||
} |
||||
// CLASS用模糊查询 |
||||
$xquery .= "[contains(@class,'".substr($s, 1)."')]"; |
||||
} |
||||
// ATTRIBUTES |
||||
else if ($s[0] == '[') |
||||
{ |
||||
if ($delimiter_before) |
||||
{ |
||||
$xquery .= '*'; |
||||
} |
||||
// strip side brackets |
||||
$attr = trim($s, ']['); |
||||
// attr with specifed value |
||||
if (mb_strpos($s, '=')) |
||||
{ |
||||
$value = null; |
||||
list($attr, $value) = explode('=', $attr); |
||||
$value = trim($value, "'\""); |
||||
if (self::is_regexp($attr)) |
||||
{ |
||||
// cut regexp character |
||||
$attr = substr($attr, 0, -1); |
||||
$xquery .= "[@{$attr}]"; |
||||
} |
||||
else |
||||
{ |
||||
$xquery .= "[@{$attr}='{$value}']"; |
||||
} |
||||
} |
||||
// attr without specified value |
||||
else |
||||
{ |
||||
$xquery .= "[@{$attr}]"; |
||||
} |
||||
} |
||||
// ~ General Sibling Selector |
||||
else if ($s[0] == '~') |
||||
{ |
||||
} |
||||
// + Adjacent sibling selectors |
||||
else if ($s[0] == '+') |
||||
{ |
||||
} |
||||
// PSEUDO CLASSES |
||||
else if ($s[0] == ':') |
||||
{ |
||||
} |
||||
// DIRECT DESCENDANDS |
||||
else if ($s == '>') |
||||
{ |
||||
$xquery .= '/'; |
||||
$delimiter_before = 2; |
||||
} |
||||
// ALL DESCENDANDS |
||||
else if ($s == ' ') |
||||
{ |
||||
$xquery .= '//'; |
||||
$delimiter_before = 2; |
||||
} |
||||
// ERRORS |
||||
else |
||||
{ |
||||
exit("Unrecognized token '$s'"); |
||||
} |
||||
$delimiter_before = $delimiter_before === 2; |
||||
} |
||||
return $xquery; |
||||
} |
||||
|
||||
/** |
||||
* @access private |
||||
*/ |
||||
public static function parse_selector($query) |
||||
{ |
||||
$query = trim( preg_replace( '@\s+@', ' ', preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) ) ); |
||||
$queries = array(); |
||||
if ( !$query ) |
||||
{ |
||||
return $queries; |
||||
} |
||||
|
||||
$special_chars = array('>',' '); |
||||
$special_chars_mapping = array(); |
||||
$strlen = mb_strlen($query); |
||||
$class_chars = array('.', '-'); |
||||
$pseudo_chars = array('-'); |
||||
$tag_chars = array('*', '|', '-'); |
||||
// split multibyte string |
||||
// http://code.google.com/p/phpquery/issues/detail?id=76 |
||||
$_query = array(); |
||||
for ( $i=0; $i<$strlen; $i++ ) |
||||
{ |
||||
$_query[] = mb_substr($query, $i, 1); |
||||
} |
||||
$query = $_query; |
||||
// it works, but i dont like it... |
||||
$i = 0; |
||||
while( $i < $strlen ) |
||||
{ |
||||
$c = $query[$i]; |
||||
$tmp = ''; |
||||
// TAG |
||||
if ( self::is_char($c) || in_array($c, $tag_chars) ) |
||||
{ |
||||
while(isset($query[$i]) && (self::is_char($query[$i]) || in_array($query[$i], $tag_chars))) |
||||
{ |
||||
$tmp .= $query[$i]; |
||||
$i++; |
||||
} |
||||
$queries[] = $tmp; |
||||
} |
||||
// IDs |
||||
else if ( $c == '#' ) |
||||
{ |
||||
$i++; |
||||
while( isset($query[$i]) && (self::is_char($query[$i]) || $query[$i] == '-') ) |
||||
{ |
||||
$tmp .= $query[$i]; |
||||
$i++; |
||||
} |
||||
$queries[] = '#'.$tmp; |
||||
} |
||||
// SPECIAL CHARS |
||||
else if ( in_array($c, $special_chars) ) |
||||
{ |
||||
$queries[] = $c; |
||||
$i++; |
||||
// MAPPED SPECIAL MULTICHARS |
||||
// } else if ( $c.$query[$i+1] == '//') { |
||||
// $return[] = ' '; |
||||
// $i = $i+2; |
||||
} |
||||
// MAPPED SPECIAL CHARS |
||||
else if ( isset($special_chars_mapping[$c])) |
||||
{ |
||||
$queries[] = $special_chars_mapping[$c]; |
||||
$i++; |
||||
} |
||||
// COMMA |
||||
else if ( $c == ',' ) |
||||
{ |
||||
$i++; |
||||
while( isset($query[$i]) && $query[$i] == ' ') |
||||
{ |
||||
$i++; |
||||
} |
||||
} |
||||
// CLASSES |
||||
else if ($c == '.') |
||||
{ |
||||
while( isset($query[$i]) && (self::is_char($query[$i]) || in_array($query[$i], $class_chars))) |
||||
{ |
||||
$tmp .= $query[$i]; |
||||
$i++; |
||||
} |
||||
$queries[] = $tmp; |
||||
} |
||||
// ~ General Sibling Selector |
||||
else if ($c == '~') |
||||
{ |
||||
$space_allowed = true; |
||||
$tmp .= $query[$i++]; |
||||
while( isset($query[$i]) |
||||
&& (self::is_char($query[$i]) |
||||
|| in_array($query[$i], $class_chars) |
||||
|| $query[$i] == '*' |
||||
|| ($query[$i] == ' ' && $space_allowed) |
||||
)) |
||||
{ |
||||
if ($query[$i] != ' ') |
||||
{ |
||||
$space_allowed = false; |
||||
} |
||||
$tmp .= $query[$i]; |
||||
$i++; |
||||
} |
||||
$queries[] = $tmp; |
||||
} |
||||
// + Adjacent sibling selectors |
||||
else if ($c == '+') |
||||
{ |
||||
$space_allowed = true; |
||||
$tmp .= $query[$i++]; |
||||
while( isset($query[$i]) |
||||
&& (self::is_char($query[$i]) |
||||
|| in_array($query[$i], $class_chars) |
||||
|| $query[$i] == '*' |
||||
|| ($space_allowed && $query[$i] == ' ') |
||||
)) |
||||
{ |
||||
if ($query[$i] != ' ') |
||||
$space_allowed = false; |
||||
$tmp .= $query[$i]; |
||||
$i++; |
||||
} |
||||
$queries[] = $tmp; |
||||
} |
||||
// ATTRS |
||||
else if ($c == '[') |
||||
{ |
||||
$stack = 1; |
||||
$tmp .= $c; |
||||
while( isset($query[++$i])) |
||||
{ |
||||
$tmp .= $query[$i]; |
||||
if ( $query[$i] == '[') |
||||
{ |
||||
$stack++; |
||||
} |
||||
else if ( $query[$i] == ']') |
||||
{ |
||||
$stack--; |
||||
if (! $stack ) |
||||
{ |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
$queries[] = $tmp; |
||||
$i++; |
||||
} |
||||
// PSEUDO CLASSES |
||||
else if ($c == ':') |
||||
{ |
||||
$stack = 1; |
||||
$tmp .= $query[$i++]; |
||||
while( isset($query[$i]) && (self::is_char($query[$i]) || in_array($query[$i], $pseudo_chars))) |
||||
{ |
||||
$tmp .= $query[$i]; |
||||
$i++; |
||||
} |
||||
// with arguments ? |
||||
if ( isset($query[$i]) && $query[$i] == '(') |
||||
{ |
||||
$tmp .= $query[$i]; |
||||
$stack = 1; |
||||
while( isset($query[++$i])) |
||||
{ |
||||
$tmp .= $query[$i]; |
||||
if ( $query[$i] == '(') |
||||
{ |
||||
$stack++; |
||||
} |
||||
else if ( $query[$i] == ')') |
||||
{ |
||||
$stack--; |
||||
if (! $stack ) |
||||
{ |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
$queries[] = $tmp; |
||||
$i++; |
||||
} |
||||
else |
||||
{ |
||||
$queries[] = $tmp; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
$i++; |
||||
} |
||||
} |
||||
|
||||
if (isset($queries[0])) |
||||
{ |
||||
if (isset($queries[0][0]) && $queries[0][0] == ':') |
||||
{ |
||||
array_unshift($queries, '*'); |
||||
} |
||||
if ($queries[0] != '>') |
||||
{ |
||||
array_unshift($queries, ' '); |
||||
} |
||||
} |
||||
|
||||
return $queries; |
||||
} |
||||
|
||||
public static function is_char($char) |
||||
{ |
||||
return preg_match('@\w@', $char); |
||||
} |
||||
|
||||
/** |
||||
* 模糊匹配 |
||||
* ^ 前缀字符串 |
||||
* * 包含字符串 |
||||
* $ 后缀字符串 |
||||
* @access private |
||||
*/ |
||||
protected static function is_regexp($pattern) |
||||
{ |
||||
return in_array( |
||||
$pattern[ mb_strlen($pattern)-1 ], |
||||
array('^','*','$') |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,936 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// PHPSpider实用函数集合类文件 |
||||
//---------------------------------- |
||||
|
||||
namespace phpspider\core; |
||||
// 引入PATH_DATA |
||||
require_once __DIR__ . '/constants.php'; |
||||
|
||||
class util |
||||
{ |
||||
/** |
||||
* 文件锁 |
||||
* 如果没有锁,就加一把锁并且执行逻辑,然后删除锁 |
||||
* if (!util::lock('statistics_offer')) |
||||
* { |
||||
* util::lock('statistics_offer'); |
||||
* ... |
||||
* util::unlock('statistics_offer'); |
||||
* } |
||||
* 否则输出锁存在 |
||||
* else |
||||
* { |
||||
* echo "process has been locked\n"; |
||||
* } |
||||
* |
||||
* @param mixed $lock_name |
||||
* @param int $lock_timeout |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-02-18 14:28 |
||||
*/ |
||||
public static function lock($lock_name, $lock_timeout = 600) |
||||
{ |
||||
$lock = util::get_file(PATH_DATA."/lock/{$lock_name}.lock"); |
||||
if ($lock) |
||||
{ |
||||
$time = time() - $lock; |
||||
// 还没到10分钟,说明进程还活着 |
||||
if ($time < $lock_timeout) |
||||
{ |
||||
return true; |
||||
} |
||||
unlink(PATH_DATA."/lock/{$lock_name}.lock"); |
||||
} |
||||
util::put_file(PATH_DATA."/lock/{$lock_name}.lock", time()); |
||||
return false; |
||||
} |
||||
|
||||
public static function unlock($lock_name) |
||||
{ |
||||
unlink(PATH_DATA."/lock/{$lock_name}.lock"); |
||||
} |
||||
|
||||
public static function time2second($time, $is_log = true) |
||||
{ |
||||
if(is_numeric($time)) |
||||
{ |
||||
$value = array( |
||||
"years" => 0, "days" => 0, "hours" => 0, |
||||
"minutes" => 0, "seconds" => 0, |
||||
); |
||||
if($time >= 31556926) |
||||
{ |
||||
$value["years"] = floor($time/31556926); |
||||
$time = ($time%31556926); |
||||
} |
||||
if($time >= 86400) |
||||
{ |
||||
$value["days"] = floor($time/86400); |
||||
$time = ($time%86400); |
||||
} |
||||
if($time >= 3600) |
||||
{ |
||||
$value["hours"] = floor($time/3600); |
||||
$time = ($time%3600); |
||||
} |
||||
if($time >= 60) |
||||
{ |
||||
$value["minutes"] = floor($time/60); |
||||
$time = ($time%60); |
||||
} |
||||
$value["seconds"] = floor($time); |
||||
//return (array) $value; |
||||
//$t = $value["years"] ."y ". $value["days"] ."d ". $value["hours"] ."h ". $value["minutes"] ."m ".$value["seconds"]."s"; |
||||
if ($is_log) |
||||
{ |
||||
$t = $value["days"] ."d ". $value["hours"] ."h ". $value["minutes"] ."m ".$value["seconds"]."s"; |
||||
} |
||||
else |
||||
{ |
||||
$t = $value["days"] ." days ". $value["hours"] ." hours ". $value["minutes"] ." minutes"; |
||||
} |
||||
return $t; |
||||
|
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
public static function get_days($day_sta, $day_end = true, $range = 86400) |
||||
{ |
||||
if ($day_end === true) $day_end = date('Y-m-d'); |
||||
|
||||
return array_map(function ($time) { |
||||
return date('Y-m-d', $time); |
||||
}, range(strtotime($day_sta), strtotime($day_end), $range)); |
||||
} |
||||
|
||||
/** |
||||
* 获取文件行数 |
||||
* |
||||
* @param mixed $filepath |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-03-31 21:54 |
||||
*/ |
||||
public static function get_file_line($filepath) |
||||
{ |
||||
$line = 0 ; |
||||
$fp = fopen($filepath , 'r'); |
||||
if (!$fp) |
||||
{ |
||||
return 0; |
||||
} |
||||
//获取文件的一行内容,注意:需要php5才支持该函数; |
||||
while( stream_get_line($fp,8192,"\n") ){ |
||||
$line++; |
||||
} |
||||
fclose($fp);//关闭文件 |
||||
return $line; |
||||
} |
||||
|
||||
/** |
||||
* 获得表数 |
||||
* |
||||
* @param mixed $table_name 表名 |
||||
* @param mixed $item_value 唯一索引 |
||||
* @param int $table_num 表数量 |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2015-10-22 23:25 |
||||
*/ |
||||
public static function get_table_num($item_value, $table_num = 100) |
||||
{ |
||||
//sha1:返回一个40字符长度的16进制数字 |
||||
$item_value = sha1(strtolower($item_value)); |
||||
//base_convert:进制建转换,下面是把16进制转成10进制,方便做除法运算 |
||||
//str_pad:把字符串填充为指定的长度,下面是在左边加0,表数量大于100就3位,否则2位 |
||||
$step = $table_num > 100 ? 3 : 2; |
||||
$item_value = str_pad(base_convert(substr($item_value, -2), 16, 10) % $table_num, $step, "0", STR_PAD_LEFT); |
||||
return $item_value; |
||||
} |
||||
|
||||
/** |
||||
* 获得表面 |
||||
* |
||||
* @param mixed $table_name 表名 |
||||
* @param mixed $item_value 唯一索引 |
||||
* @param int $table_num 表数量 |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2015-10-22 23:25 |
||||
*/ |
||||
public static function get_table_name($table_name, $item_value, $table_num = 100) |
||||
{ |
||||
//sha1:返回一个40字符长度的16进制数字 |
||||
$item_value = sha1(strtolower($item_value)); |
||||
//base_convert:进制建转换,下面是把16进制转成10进制,方便做除法运算 |
||||
//str_pad:把字符串填充为指定的长度,下面是在左边加0,共3位 |
||||
$step = $table_num > 100 ? 3 : 2; |
||||
$item_value = str_pad(base_convert(substr($item_value, -2), 16, 10) % $table_num, $step, "0", STR_PAD_LEFT); |
||||
return $table_name."_".$item_value; |
||||
} |
||||
|
||||
// 获得当前使用内存 |
||||
public static function memory_get_usage() |
||||
{ |
||||
$memory = memory_get_usage(); |
||||
return self::format_bytes($memory); |
||||
} |
||||
|
||||
// 获得最高使用内存 |
||||
public static function memory_get_peak_usage() |
||||
{ |
||||
$memory = memory_get_peak_usage(); |
||||
return self::format_bytes($memory); |
||||
} |
||||
|
||||
// 转换大小单位 |
||||
public static function format_bytes($size) |
||||
{ |
||||
$unit = array('b', 'kb', 'mb', 'gb', 'tb', 'pb'); |
||||
return @round($size / pow(1024, ($i = floor(log($size, 1024)))), 2) . ' ' . $unit[$i]; |
||||
} |
||||
|
||||
/** |
||||
* 获取数组大小 |
||||
* |
||||
* @param mixed $arr 数组 |
||||
* @return string |
||||
*/ |
||||
public static function array_size($arr) |
||||
{ |
||||
ob_start(); |
||||
print_r($arr); |
||||
$mem = ob_get_contents(); |
||||
ob_end_clean(); |
||||
$mem = preg_replace("/\n +/", "", $mem); |
||||
$mem = strlen($mem); |
||||
return self::format_bytes($mem); |
||||
} |
||||
|
||||
/** |
||||
* 数字随机数 |
||||
* |
||||
* @param int $num |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public static function rand_num($num = 7) |
||||
{ |
||||
$rand = ""; |
||||
for ($i = 0; $i < $num; $i ++) |
||||
{ |
||||
$rand .= mt_rand(0, 9); |
||||
} |
||||
return $rand; |
||||
} |
||||
|
||||
/** |
||||
* 字母数字混合随机数 |
||||
* |
||||
* @param int $num |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public static function rand_str($num = 10) |
||||
{ |
||||
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; |
||||
$string = ""; |
||||
for ($i = 0; $i < $num; $i ++) |
||||
{ |
||||
$string .= substr($chars, rand(0, strlen($chars)), 1); |
||||
} |
||||
return $string; |
||||
} |
||||
|
||||
/** |
||||
* 汉字转拼音 |
||||
* |
||||
* @param mixed $str 汉字 |
||||
* @param int $ishead |
||||
* @param int $isclose |
||||
* @static |
||||
* @access public |
||||
* @return string |
||||
*/ |
||||
public static function pinyin($str, $ishead = 0, $isclose = 1) |
||||
{ |
||||
// $str = iconv("utf-8", "gbk//ignore", $str); |
||||
$str = mb_convert_encoding($str, "gbk", "utf-8"); |
||||
global $pinyins; |
||||
$restr = ''; |
||||
$str = trim($str); |
||||
$slen = strlen($str); |
||||
if ($slen < 2) |
||||
{ |
||||
return $str; |
||||
} |
||||
if (count($pinyins) == 0) |
||||
{ |
||||
$fp = fopen(PATH_DATA . '/pinyin.dat', 'r'); |
||||
while (!feof($fp)) |
||||
{ |
||||
$line = trim(fgets($fp)); |
||||
$pinyins[$line[0] . $line[1]] = substr($line, 3, strlen($line) - 3); |
||||
} |
||||
fclose($fp); |
||||
} |
||||
for ($i = 0; $i < $slen; $i ++) |
||||
{ |
||||
if (ord($str[$i]) > 0x80) |
||||
{ |
||||
$c = $str[$i] . $str[$i + 1]; |
||||
$i ++; |
||||
if (isset($pinyins[$c])) |
||||
{ |
||||
if ($ishead == 0) |
||||
{ |
||||
$restr .= $pinyins[$c]; |
||||
} |
||||
else |
||||
{ |
||||
$restr .= $pinyins[$c][0]; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
// $restr .= "_"; |
||||
} |
||||
} |
||||
else if (preg_match("/[a-z0-9]/i", $str[$i])) |
||||
{ |
||||
$restr .= $str[$i]; |
||||
} |
||||
else |
||||
{ |
||||
// $restr .= "_"; |
||||
} |
||||
} |
||||
if ($isclose == 0) |
||||
{ |
||||
unset($pinyins); |
||||
} |
||||
return $restr; |
||||
} |
||||
|
||||
/** |
||||
* 生成字母前缀 |
||||
* |
||||
* @param mixed $s0 |
||||
* @return char |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public static function letter_first($s0) |
||||
{ |
||||
$firstchar_ord = ord(strtoupper($s0{0})); |
||||
if (($firstchar_ord >= 65 and $firstchar_ord <= 91) or ($firstchar_ord >= 48 and $firstchar_ord <= 57)) return $s0{0}; |
||||
// $s = iconv("utf-8", "gbk//ignore", $s0); |
||||
$s = mb_convert_encoding($s0, "gbk", "utf-8"); |
||||
$asc = ord($s{0}) * 256 + ord($s{1}) - 65536; |
||||
if ($asc >= -20319 and $asc <= -20284) return "A"; |
||||
if ($asc >= -20283 and $asc <= -19776) return "B"; |
||||
if ($asc >= -19775 and $asc <= -19219) return "C"; |
||||
if ($asc >= -19218 and $asc <= -18711) return "D"; |
||||
if ($asc >= -18710 and $asc <= -18527) return "E"; |
||||
if ($asc >= -18526 and $asc <= -18240) return "F"; |
||||
if ($asc >= -18239 and $asc <= -17923) return "G"; |
||||
if ($asc >= -17922 and $asc <= -17418) return "H"; |
||||
if ($asc >= -17417 and $asc <= -16475) return "J"; |
||||
if ($asc >= -16474 and $asc <= -16213) return "K"; |
||||
if ($asc >= -16212 and $asc <= -15641) return "L"; |
||||
if ($asc >= -15640 and $asc <= -15166) return "M"; |
||||
if ($asc >= -15165 and $asc <= -14923) return "N"; |
||||
if ($asc >= -14922 and $asc <= -14915) return "O"; |
||||
if ($asc >= -14914 and $asc <= -14631) return "P"; |
||||
if ($asc >= -14630 and $asc <= -14150) return "Q"; |
||||
if ($asc >= -14149 and $asc <= -14091) return "R"; |
||||
if ($asc >= -14090 and $asc <= -13319) return "S"; |
||||
if ($asc >= -13318 and $asc <= -12839) return "T"; |
||||
if ($asc >= -12838 and $asc <= -12557) return "W"; |
||||
if ($asc >= -12556 and $asc <= -11848) return "X"; |
||||
if ($asc >= -11847 and $asc <= -11056) return "Y"; |
||||
if ($asc >= -11055 and $asc <= -10247) return "Z"; |
||||
return 0; // null |
||||
} |
||||
|
||||
/** |
||||
* 获得某天前的时间戳 |
||||
* |
||||
* @param mixed $day |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public static function getxtime($day) |
||||
{ |
||||
$day = intval($day); |
||||
return mktime(23, 59, 59, date("m"), date("d") - $day, date("y")); |
||||
} |
||||
|
||||
/** |
||||
* 读文件 |
||||
*/ |
||||
public static function get_file($url, $timeout = 10) |
||||
{ |
||||
if (function_exists('curl_init')) |
||||
{ |
||||
$ch = curl_init(); |
||||
curl_setopt($ch, CURLOPT_URL, $url); |
||||
curl_setopt($ch, CURLOPT_HEADER, 0); |
||||
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); |
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); |
||||
$content = curl_exec($ch); |
||||
curl_close($ch); |
||||
if ($content) return $content; |
||||
} |
||||
$ctx = stream_context_create(array('http' => array('timeout' => $timeout))); |
||||
$content = @file_get_contents($url, 0, $ctx); |
||||
if ($content) return $content; |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* 写文件,如果文件目录不存在,则递归生成 |
||||
*/ |
||||
public static function put_file($file, $content, $flag = 0) |
||||
{ |
||||
$pathinfo = pathinfo($file); |
||||
if (!empty($pathinfo['dirname'])) |
||||
{ |
||||
if (file_exists($pathinfo['dirname']) === false) |
||||
{ |
||||
if (@mkdir($pathinfo['dirname'], 0777, true) === false) |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
if ($flag === FILE_APPEND) |
||||
{ |
||||
// 多个php-fpm写一个文件的时候容易丢失,要加锁 |
||||
//return @file_put_contents($file, $content, FILE_APPEND|LOCK_EX); |
||||
return @file_put_contents($file, $content, FILE_APPEND); |
||||
} |
||||
else |
||||
{ |
||||
return @file_put_contents($file, $content, LOCK_EX); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 检查路径是否存在,不存在则递归生成路径 |
||||
* |
||||
* @param mixed $path 路径 |
||||
* @static |
||||
* @access public |
||||
* @return bool or string |
||||
*/ |
||||
public static function path_exists($path) |
||||
{ |
||||
$pathinfo = pathinfo($path . '/tmp.txt'); |
||||
if (!empty($pathinfo['dirname'])) |
||||
{ |
||||
if (file_exists($pathinfo['dirname']) === false) |
||||
{ |
||||
if (mkdir($pathinfo['dirname'], 0777, true) === false) |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
return $path; |
||||
} |
||||
|
||||
/** |
||||
* 递归删除目录 |
||||
* |
||||
* @param mixed $dir |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public static function deldir($dir) |
||||
{ |
||||
//先删除目录下的文件: |
||||
$dh = opendir($dir); |
||||
while ($file = readdir($dh)) |
||||
{ |
||||
if($file!="." && $file!="..") |
||||
{ |
||||
$fullpath = $dir."/".$file; |
||||
if(!is_dir($fullpath)) |
||||
{ |
||||
unlink($fullpath); |
||||
} |
||||
else |
||||
{ |
||||
self::deldir($fullpath); |
||||
} |
||||
} |
||||
} |
||||
|
||||
closedir($dh); |
||||
//删除当前文件夹: |
||||
if(rmdir($dir)) |
||||
{ |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 递归修改目录权限 |
||||
* |
||||
* @param mixed $path 目录 |
||||
* @param mixed $filemode 权限 |
||||
* @return bool |
||||
*/ |
||||
public static function chmodr($path, $filemode) |
||||
{ |
||||
if (!is_dir($path)) |
||||
{ |
||||
return @chmod($path, $filemode); |
||||
} |
||||
|
||||
$dh = opendir($path); |
||||
while (($file = readdir($dh)) !== false) |
||||
{ |
||||
if ($file != '.' && $file != '..') |
||||
{ |
||||
$fullpath = $path . '/' . $file; |
||||
if (is_link($fullpath)) |
||||
{ |
||||
return FALSE; |
||||
} |
||||
elseif (!is_dir($fullpath) && !@chmod($fullpath, $filemode)) |
||||
{ |
||||
return FALSE; |
||||
} |
||||
elseif (!self::chmodr($fullpath, $filemode)) |
||||
{ |
||||
return FALSE; |
||||
} |
||||
} |
||||
} |
||||
|
||||
closedir($dh); |
||||
|
||||
if (@chmod($path, $filemode)) |
||||
{ |
||||
return TRUE; |
||||
} |
||||
else |
||||
{ |
||||
return FALSE; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 数组格式化为CSV |
||||
* |
||||
* @param mixed $data |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-07-29 11:32 |
||||
*/ |
||||
public static function format_csv($data) |
||||
{ |
||||
foreach ($data as $k=>$v) |
||||
{ |
||||
$v = str_replace(",", "", $v); |
||||
$v = str_replace(",", "", $v); |
||||
$data[$k] = $v; |
||||
} |
||||
return implode(",", $data); |
||||
} |
||||
|
||||
/** |
||||
* 判断是否为utf8字符串 |
||||
* @parem $str |
||||
* @return bool |
||||
*/ |
||||
public static function is_utf8($str) |
||||
{ |
||||
if ($str === mb_convert_encoding(mb_convert_encoding($str, "UTF-32", "UTF-8"), "UTF-8", "UTF-32")) |
||||
{ |
||||
return true; |
||||
} |
||||
else |
||||
{ |
||||
return false; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取文件编码 |
||||
* @param $string |
||||
* @return string |
||||
*/ |
||||
public static function get_encoding($string) |
||||
{ |
||||
$encoding = mb_detect_encoding($string, array('UTF-8', 'GBK', 'GB2312', 'LATIN1', 'ASCII', 'BIG5')); |
||||
return strtolower($encoding); |
||||
} |
||||
|
||||
/** |
||||
* 转换数组值的编码格式 |
||||
* @param array $arr |
||||
* @param string $toEncoding |
||||
* @param string $fromEncoding |
||||
* @return array |
||||
*/ |
||||
public static function array_iconv($arr, $from_encoding, $to_encoding) |
||||
{ |
||||
eval('$arr = '.iconv($from_encoding, $to_encoding.'//IGNORE', var_export($arr,TRUE)).';'); |
||||
return $arr; |
||||
} |
||||
|
||||
/** |
||||
* 从普通时间返回Linux时间截(strtotime中文处理版) |
||||
* @parem string $dtime |
||||
* @return int |
||||
*/ |
||||
public static function cn_strtotime($dtime) |
||||
{ |
||||
if (!preg_match("/[^0-9]/", $dtime)) |
||||
{ |
||||
return $dtime; |
||||
} |
||||
$dtime = trim($dtime); |
||||
$dt = Array(1970, 1, 1, 0, 0, 0); |
||||
$dtime = preg_replace("/[\r\n\t]|日|秒/", " ", $dtime); |
||||
$dtime = str_replace("年", "-", $dtime); |
||||
$dtime = str_replace("月", "-", $dtime); |
||||
$dtime = str_replace("时", ":", $dtime); |
||||
$dtime = str_replace("分", ":", $dtime); |
||||
$dtime = trim(preg_replace("/[ ]{1,}/", " ", $dtime)); |
||||
$ds = explode(" ", $dtime); |
||||
$ymd = explode("-", $ds[0]); |
||||
if (!isset($ymd[1])) |
||||
{ |
||||
$ymd = explode(".", $ds[0]); |
||||
} |
||||
if (isset($ymd[0])) |
||||
{ |
||||
$dt[0] = $ymd[0]; |
||||
} |
||||
if (isset($ymd[1])) $dt[1] = $ymd[1]; |
||||
if (isset($ymd[2])) $dt[2] = $ymd[2]; |
||||
if (strlen($dt[0]) == 2) $dt[0] = '20' . $dt[0]; |
||||
if (isset($ds[1])) |
||||
{ |
||||
$hms = explode(":", $ds[1]); |
||||
if (isset($hms[0])) $dt[3] = $hms[0]; |
||||
if (isset($hms[1])) $dt[4] = $hms[1]; |
||||
if (isset($hms[2])) $dt[5] = $hms[2]; |
||||
} |
||||
foreach ($dt as $k => $v) |
||||
{ |
||||
$v = preg_replace("/^0{1,}/", '', trim($v)); |
||||
if ($v == '') |
||||
{ |
||||
$dt[$k] = 0; |
||||
} |
||||
} |
||||
$mt = mktime($dt[3], $dt[4], $dt[5], $dt[1], $dt[2], $dt[0]); |
||||
if (!empty($mt)) |
||||
{ |
||||
return $mt; |
||||
} |
||||
else |
||||
{ |
||||
return strtotime($dtime); |
||||
} |
||||
} |
||||
|
||||
public static function cn_substr($string, $length = 80, $etc = '...', $count_words = true) |
||||
{ |
||||
mb_internal_encoding("UTF-8"); |
||||
if ($length == 0) return ''; |
||||
if (strlen($string) <= $length) return $string; |
||||
preg_match_all("/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|\xe0[\xa0-\xbf][\x80-\xbf]|[\xe1-\xef][\x80-\xbf][\x80-\xbf]|\xf0[\x90-\xbf][\x80-\xbf][\x80-\xbf]|[\xf1-\xf7][\x80-\xbf][\x80-\xbf][\x80-\xbf]/", $string, $info); |
||||
if ($count_words) |
||||
{ |
||||
$j = 0; |
||||
$wordscut = ""; |
||||
for ($i = 0; $i < count($info[0]); $i ++) |
||||
{ |
||||
$wordscut .= $info[0][$i]; |
||||
if (ord($info[0][$i]) >= 128) |
||||
{ |
||||
$j = $j + 2; |
||||
} |
||||
else |
||||
{ |
||||
$j = $j + 1; |
||||
} |
||||
if ($j >= $length) |
||||
{ |
||||
return $wordscut . $etc; |
||||
} |
||||
} |
||||
return join('', $info[0]); |
||||
} |
||||
return join("", array_slice($info[0], 0, $length)) . $etc; |
||||
} |
||||
|
||||
/** |
||||
* 获取文件后缀名 |
||||
* |
||||
* @param mixed $file_name 文件名 |
||||
* @static |
||||
* |
||||
* @access public |
||||
* @return string |
||||
*/ |
||||
public static function get_extension($file_name) |
||||
{ |
||||
$ext = explode('.', $file_name); |
||||
$ext = array_pop($ext); |
||||
return strtolower($ext); |
||||
} |
||||
|
||||
// 获取 Url 跳转后的真实地址 |
||||
public static function getrealurl($url) |
||||
{ |
||||
if (empty($url)) |
||||
{ |
||||
return $url; |
||||
} |
||||
$header = get_headers($url, 1); |
||||
if (empty($header[0]) || empty($header[1])) |
||||
{ |
||||
return $url; |
||||
} |
||||
if (strpos($header[0], '301') || strpos($header[0], '302')) |
||||
{ |
||||
if (empty($header['Location'])) |
||||
{ |
||||
return $url; |
||||
} |
||||
if (is_array($header['Location'])) |
||||
{ |
||||
return $header['Location'][count($header['Location']) - 1]; |
||||
} |
||||
else |
||||
{ |
||||
return $header['Location']; |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
return $url; |
||||
} |
||||
} |
||||
|
||||
// 解压服务器用 Content-Encoding:gzip 压缩过的数据 |
||||
public static function gzdecode($data) |
||||
{ |
||||
$flags = ord(substr($data, 3, 1)); |
||||
$headerlen = 10; |
||||
$extralen = 0; |
||||
$filenamelen = 0; |
||||
if ($flags & 4) |
||||
{ |
||||
$extralen = unpack('v', substr($data, 10, 2)); |
||||
$extralen = $extralen[1]; |
||||
$headerlen += 2 + $extralen; |
||||
} |
||||
if ($flags & 8) // Filename |
||||
$headerlen = strpos($data, chr(0), $headerlen) + 1; |
||||
if ($flags & 16) // Comment |
||||
$headerlen = strpos($data, chr(0), $headerlen) + 1; |
||||
if ($flags & 2) // CRC at end of file |
||||
$headerlen += 2; |
||||
$unpacked = @gzinflate(substr($data, $headerlen)); |
||||
if ($unpacked === FALSE) $unpacked = $data; |
||||
return $unpacked; |
||||
} |
||||
|
||||
/** |
||||
* 数字金额转换为中文 |
||||
* @param string|integer|float $num 目标数字 |
||||
* @param boolean $sim 使用小写(默认) |
||||
* @return string |
||||
*/ |
||||
public static function number2chinese($num, $sim = FALSE) |
||||
{ |
||||
if (!is_numeric($num)) return '含有非数字非小数点字符!'; |
||||
$char = $sim ? array('零', '一', '二', '三', '四', '五', '六', '七', '八', '九') : array('零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖'); |
||||
$unit = $sim ? array('', '十', '百', '千', '', '万', '亿', '兆') : array('', '拾', '佰', '仟', '', '萬', '億', '兆'); |
||||
$retval = ''; |
||||
|
||||
$num = sprintf("%01.2f", $num); |
||||
|
||||
list ($num, $dec) = explode('.', $num); |
||||
|
||||
// 小数部分 |
||||
if ($dec['0'] > 0) |
||||
{ |
||||
$retval .= "{$char[$dec['0']]}角"; |
||||
} |
||||
if ($dec['1'] > 0) |
||||
{ |
||||
$retval .= "{$char[$dec['1']]}分"; |
||||
} |
||||
|
||||
// 整数部分 |
||||
if ($num > 0) |
||||
{ |
||||
$retval = "元" . $retval; |
||||
$f = 1; |
||||
$str = strrev(intval($num)); |
||||
for ($i = 0, $c = strlen($str); $i < $c; $i ++) |
||||
{ |
||||
if ($str[$i] > 0) |
||||
{ |
||||
$f = 0; |
||||
} |
||||
if ($f == 1 && $str[$i] == 0) |
||||
{ |
||||
$out[$i] = ""; |
||||
} |
||||
else |
||||
{ |
||||
$out[$i] = $char[$str[$i]]; |
||||
} |
||||
$out[$i] .= $str[$i] != '0' ? $unit[$i % 4] : ''; |
||||
if ($i > 1 and $str[$i] + $str[$i - 1] == 0) |
||||
{ |
||||
$out[$i] = ''; |
||||
} |
||||
if ($i % 4 == 0) |
||||
{ |
||||
$out[$i] .= $unit[4 + floor($i / 4)]; |
||||
} |
||||
} |
||||
$retval = join('', array_reverse($out)) . $retval; |
||||
} |
||||
return $retval; |
||||
} |
||||
|
||||
public static function colorize($str, $status = "info") |
||||
{ |
||||
$out = ""; |
||||
switch ($status) |
||||
{ |
||||
case 'succ': |
||||
$out = "\033[32m"; // Blue |
||||
break; |
||||
case "error": |
||||
$out = "\033[31m"; // Red |
||||
break; |
||||
case "warn": |
||||
$out = "\033[33m"; // Yellow |
||||
break; |
||||
case "note": |
||||
$out = "\033[34m"; // Green |
||||
break; |
||||
case "debug": |
||||
$out = "\033[36m"; // Green |
||||
break; |
||||
default: |
||||
$out = "\033[0m"; // info |
||||
break; |
||||
} |
||||
return $out.$str."\033[0m"; |
||||
} |
||||
|
||||
public static function node_to_array($dom, $node) |
||||
{ |
||||
if(!is_a( $dom, 'DOMDocument' ) || !is_a( $node, 'DOMNode' )) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
$array = array(); |
||||
// Discard empty nodes |
||||
$localName = trim( $node->localName ); |
||||
if( empty($localName)) |
||||
{ |
||||
return false; |
||||
} |
||||
if( XML_TEXT_NODE == $node->nodeType ) |
||||
{ |
||||
return $node->nodeValue; |
||||
} |
||||
foreach ($node->attributes as $attr) |
||||
{ |
||||
$array['@'.$attr->localName] = $attr->nodeValue; |
||||
} |
||||
foreach ($node->childNodes as $childNode) |
||||
{ |
||||
if ( (isset($childNode->childNodes->length) && 1 == $childNode->childNodes->length) && |
||||
XML_TEXT_NODE == $childNode->firstChild->nodeType ) |
||||
{ |
||||
$array[$childNode->localName] = $childNode->nodeValue; |
||||
} |
||||
else |
||||
{ |
||||
if( false !== ($a = self::node_to_array( $dom, $childNode))) |
||||
{ |
||||
$array[$childNode->localName] = $a; |
||||
} |
||||
} |
||||
} |
||||
return $array; |
||||
} |
||||
|
||||
public static function is_win() |
||||
{ |
||||
return strtoupper(substr(PHP_OS,0,3))==="WIN"; |
||||
} |
||||
|
||||
/** |
||||
* 和 http_build_query 相反,分解出参数 |
||||
* |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-05-16 17:29 |
||||
*/ |
||||
public static function http_split_query($query, $is_query = false) |
||||
{ |
||||
if (!$is_query) |
||||
{ |
||||
$parse_arr = parse_url($query); |
||||
if (empty($parse_arr['query'])) |
||||
{ |
||||
return array(); |
||||
} |
||||
$query = $parse_arr['query']; |
||||
} |
||||
|
||||
$query_arr = explode("&", $query); |
||||
$params = array(); |
||||
foreach ($query_arr as $val) |
||||
{ |
||||
$arr = explode("=", $val); |
||||
$params[$arr[0]] = $arr[1]; |
||||
} |
||||
return $params; |
||||
} |
||||
} |
||||
|
||||
|
@ -0,0 +1,421 @@ |
||||
<?php |
||||
// +---------------------------------------------------------------------- |
||||
// | PHPSpider [ A PHP Framework For Crawler ] |
||||
// +---------------------------------------------------------------------- |
||||
// | Copyright (c) 2006-2014 https://doc.phpspider.org All rights reserved. |
||||
// +---------------------------------------------------------------------- |
||||
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 ) |
||||
// +---------------------------------------------------------------------- |
||||
// | Author: Seatle Yang <seatle@foxmail.com> |
||||
// +---------------------------------------------------------------------- |
||||
|
||||
//---------------------------------- |
||||
// Worker多进程操作类 |
||||
//---------------------------------- |
||||
|
||||
class worker |
||||
{ |
||||
// worker进程数 |
||||
public $count = 0; |
||||
// worker id,worker进程从1开始,0被master进程所使用 |
||||
public $worker_id = 0; |
||||
// worker 进程ID |
||||
public $worker_pid = 0; |
||||
// 进程用户 |
||||
public $user = ''; |
||||
// 进程名 |
||||
public $title = ''; |
||||
// 每个进程是否只运行一次 |
||||
public $run_once = true; |
||||
// 是否输出日志 |
||||
public $log_show = false; |
||||
// master进程启动回调 |
||||
public $on_start = false; |
||||
// master进程停止回调 |
||||
public $on_stop = false; |
||||
// worker进程启动回调 |
||||
public $on_worker_start = false; |
||||
// worker进程停止回调 |
||||
public $on_worker_stop = false; |
||||
// master进程ID |
||||
protected static $_master_pid = 0; |
||||
// worker进程ID |
||||
protected static $_worker_pids = array(); |
||||
// master、worker进程启动时间 |
||||
public $time_start = 0; |
||||
// master、worker进程运行状态 [starting|running|shutdown|reload] |
||||
protected static $_status = "starting"; |
||||
|
||||
|
||||
public function __construct() |
||||
{ |
||||
self::$_master_pid = posix_getpid(); |
||||
// 产生时钟云,添加后父进程才可以收到信号 |
||||
declare(ticks = 1); |
||||
$this->install_signal(); |
||||
} |
||||
|
||||
/** |
||||
* 安装信号处理函数 |
||||
* @return void |
||||
*/ |
||||
protected function install_signal() |
||||
{ |
||||
// stop |
||||
pcntl_signal(SIGINT, array($this, 'signal_handler'), false); |
||||
// reload |
||||
pcntl_signal(SIGUSR1, array($this, 'signal_handler'), false); |
||||
// status |
||||
pcntl_signal(SIGUSR2, array($this, 'signal_handler'), false); |
||||
// ignore |
||||
pcntl_signal(SIGPIPE, SIG_IGN, false); |
||||
// install signal handler for dead kids |
||||
// pcntl_signal(SIGCHLD, array($this, 'signal_handler')); |
||||
} |
||||
|
||||
/** |
||||
* 卸载信号处理函数 |
||||
* @return void |
||||
*/ |
||||
protected function uninstall_signal() |
||||
{ |
||||
// uninstall stop signal handler |
||||
pcntl_signal(SIGINT, SIG_IGN, false); |
||||
// uninstall reload signal handler |
||||
pcntl_signal(SIGUSR1, SIG_IGN, false); |
||||
// uninstall status signal handler |
||||
pcntl_signal(SIGUSR2, SIG_IGN, false); |
||||
} |
||||
|
||||
/** |
||||
* 信号处理函数,会被其他类调用到,所以要设置为public |
||||
* @param int $signal |
||||
*/ |
||||
public function signal_handler($signal) { |
||||
switch ($signal) { |
||||
// stop 2 |
||||
case SIGINT: |
||||
// master进程和worker进程都会调用 |
||||
$this->stop_all(); |
||||
break; |
||||
// reload 30 |
||||
case SIGUSR1: |
||||
echo "reload\n"; |
||||
break; |
||||
// show status 31 |
||||
case SIGUSR2: |
||||
echo "status\n"; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 运行worker实例 |
||||
*/ |
||||
public function run() |
||||
{ |
||||
$this->time_start = microtime(true); |
||||
$this->worker_id = 0; |
||||
$this->worker_pid = posix_getpid(); |
||||
$this->set_process_title($this->title); |
||||
|
||||
// 这里赋值,worker进程也会克隆到 |
||||
if ($this->log_show) |
||||
{ |
||||
log::$log_show = true; |
||||
} |
||||
|
||||
if ($this->on_start) |
||||
{ |
||||
call_user_func($this->on_start, $this); |
||||
} |
||||
|
||||
// worker进程从1开始,0被master进程所使用 |
||||
for ($i = 1; $i <= $this->count; $i++) |
||||
{ |
||||
$this->fork_one_worker($i); |
||||
} |
||||
$this->monitor_workers(); |
||||
} |
||||
|
||||
/** |
||||
* 创建一个子进程 |
||||
* @param Worker $worker |
||||
* @throws Exception |
||||
*/ |
||||
public function fork_one_worker($worker_id) |
||||
{ |
||||
//$sockets = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); |
||||
$pid = pcntl_fork(); |
||||
|
||||
// 主进程记录子进程pid |
||||
if($pid > 0) |
||||
{ |
||||
self::$_worker_pids[$worker_id] = $pid; |
||||
} |
||||
// 子进程运行 |
||||
elseif(0 === $pid) |
||||
{ |
||||
$this->time_start = microtime(true); |
||||
$this->worker_id = $worker_id; |
||||
$this->worker_pid = posix_getpid(); |
||||
$this->set_process_title($this->title); |
||||
$this->set_process_user($this->user); |
||||
// 清空master进程克隆过来的worker进程ID |
||||
self::$_worker_pids = array(); |
||||
//$this->uninstall_signal(); |
||||
|
||||
// 设置worker进程的运行状态为运行中 |
||||
self::$_status = "running"; |
||||
|
||||
// 注册进程退出回调,用来检查是否有错误(子进程里面注册) |
||||
register_shutdown_function(array($this, 'check_errors')); |
||||
|
||||
// 如果设置了worker进程启动回调函数 |
||||
if ($this->on_worker_start) |
||||
{ |
||||
call_user_func($this->on_worker_start, $this); |
||||
} |
||||
|
||||
// 停止当前worker实例 |
||||
$this->stop(); |
||||
// 这里用0表示正常退出 |
||||
exit(0); |
||||
} |
||||
else |
||||
{ |
||||
log::add("fork one worker fail", "Error"); |
||||
exit; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 尝试设置运行当前进程的用户 |
||||
* |
||||
* @param $user_name |
||||
*/ |
||||
protected static function set_process_user($user_name) |
||||
{ |
||||
// 用户名为空 或者 当前用户不是root用户 |
||||
if(empty($user_name) || posix_getuid() !== 0) |
||||
{ |
||||
return; |
||||
} |
||||
$user_info = posix_getpwnam($user_name); |
||||
if($user_info['uid'] != posix_getuid() || $user_info['gid'] != posix_getgid()) |
||||
{ |
||||
if(!posix_setgid($user_info['gid']) || !posix_setuid($user_info['uid'])) |
||||
{ |
||||
log::add('Can not run woker as '.$user_name." , You shuld be root", "Error"); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 设置当前进程的名称,在ps aux命令中有用 |
||||
* 注意 需要php>=5.5或者安装了protitle扩展 |
||||
* @param string $title |
||||
* @return void |
||||
*/ |
||||
protected function set_process_title($title) |
||||
{ |
||||
if (!empty($title)) |
||||
{ |
||||
// 需要扩展 |
||||
if(extension_loaded('proctitle') && function_exists('setproctitle')) |
||||
{ |
||||
@setproctitle($title); |
||||
} |
||||
// >=php 5.5 |
||||
elseif (function_exists('cli_set_process_title')) |
||||
{ |
||||
cli_set_process_title($title); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 监控所有子进程的退出事件及退出码 |
||||
* @return void |
||||
*/ |
||||
public function monitor_workers() |
||||
{ |
||||
// 设置master进程的运行状态为运行中 |
||||
self::$_status = "running"; |
||||
while(1) |
||||
{ |
||||
// pcntl_signal_dispatch 子进程无法接受到信号 |
||||
// 如果有信号到来,尝试触发信号处理函数 |
||||
//pcntl_signal_dispatch(); |
||||
// 挂起进程,直到有子进程退出或者被信号打断 |
||||
$status = 0; |
||||
$pid = pcntl_wait($status, WUNTRACED); |
||||
// 如果有信号到来,尝试触发信号处理函数 |
||||
//pcntl_signal_dispatch(); |
||||
|
||||
// 子进程退出信号 |
||||
if($pid > 0) |
||||
{ |
||||
//echo "worker[".$pid."] stop\n"; |
||||
//$this->stop(); |
||||
|
||||
// 如果不是正常退出,是被kill等杀掉的 |
||||
if($status !== 0) |
||||
{ |
||||
log::add("worker {$pid} exit with status $status", "Warning"); |
||||
} |
||||
|
||||
// key 和 value 互换 |
||||
$worker_pids = array_flip(self::$_worker_pids); |
||||
// 通过 pid 得到 worker_id |
||||
$worker_id = $worker_pids[$pid]; |
||||
// 这里不unset掉,是为了进程重启 |
||||
self::$_worker_pids[$worker_id] = 0; |
||||
//unset(self::$_worker_pids[$pid]); |
||||
|
||||
// 再生成一个worker |
||||
if (!$this->run_once) |
||||
{ |
||||
$this->fork_one_worker($worker_id); |
||||
} |
||||
|
||||
// 如果所有子进程都退出了,触发主进程退出函数 |
||||
$all_worker_stop = true; |
||||
foreach (self::$_worker_pids as $_worker_pid) |
||||
{ |
||||
// 只要有一个worker进程还存在进程ID,就不算退出 |
||||
if ($_worker_pid != 0) |
||||
{ |
||||
$all_worker_stop = false; |
||||
} |
||||
} |
||||
if ($all_worker_stop) |
||||
{ |
||||
if ($this->on_stop) |
||||
{ |
||||
call_user_func($this->on_stop, $this); |
||||
} |
||||
exit(0); |
||||
} |
||||
} |
||||
// 其他信号 |
||||
else |
||||
{ |
||||
// worker进程接受到master进行信号退出的,会到这里来 |
||||
if ($this->on_stop) |
||||
{ |
||||
call_user_func($this->on_stop, $this); |
||||
} |
||||
exit(0); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 执行关闭流程(所有进程) |
||||
* 事件触发,非正常程序执行完毕 |
||||
* @return void |
||||
*/ |
||||
public function stop_all() |
||||
{ |
||||
// 设置master、worker进程的运行状态为关闭状态 |
||||
self::$_status = "shutdown"; |
||||
// master进程 |
||||
if(self::$_master_pid === posix_getpid()) |
||||
{ |
||||
// 循环给worker进程发送关闭信号 |
||||
foreach (self::$_worker_pids as $worker_pid) |
||||
{ |
||||
posix_kill($worker_pid, SIGINT); |
||||
} |
||||
} |
||||
// worker进程 |
||||
else |
||||
{ |
||||
// 接收到master进程发送的关闭信号之后退出,这里应该考虑业务的完整性,不能强行exit |
||||
$this->stop(); |
||||
exit(0); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 停止当前worker实例 |
||||
* 正常运行结束和接受信号退出,都会调用这个方法 |
||||
* @return void |
||||
*/ |
||||
public function stop() |
||||
{ |
||||
if ($this->on_worker_stop) |
||||
{ |
||||
call_user_func($this->on_worker_stop, $this); |
||||
} |
||||
// 设置worker进程的运行状态为关闭 |
||||
self::$_status = "shutdown"; |
||||
} |
||||
|
||||
/** |
||||
* 检查错误,PHP exit之前会执行 |
||||
* @return void |
||||
*/ |
||||
public function check_errors() |
||||
{ |
||||
// 如果当前worker进程不是正常退出 |
||||
if(self::$_status != "shutdown") |
||||
{ |
||||
$error_msg = "WORKER EXIT UNEXPECTED "; |
||||
$errors = error_get_last(); |
||||
if($errors && ($errors['type'] === E_ERROR || |
||||
$errors['type'] === E_PARSE || |
||||
$errors['type'] === E_CORE_ERROR || |
||||
$errors['type'] === E_COMPILE_ERROR || |
||||
$errors['type'] === E_RECOVERABLE_ERROR )) |
||||
{ |
||||
$error_msg .= $this->get_error_type($errors['type']) . " {$errors['message']} in {$errors['file']} on line {$errors['line']}"; |
||||
} |
||||
log::add($error_msg, 'Error'); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 获取错误类型对应的意义 |
||||
* @param integer $type |
||||
* @return string |
||||
*/ |
||||
protected function get_error_type($type) |
||||
{ |
||||
switch($type) |
||||
{ |
||||
case E_ERROR: // 1 // |
||||
return 'E_ERROR'; |
||||
case E_WARNING: // 2 // |
||||
return 'E_WARNING'; |
||||
case E_PARSE: // 4 // |
||||
return 'E_PARSE'; |
||||
case E_NOTICE: // 8 // |
||||
return 'E_NOTICE'; |
||||
case E_CORE_ERROR: // 16 // |
||||
return 'E_CORE_ERROR'; |
||||
case E_CORE_WARNING: // 32 // |
||||
return 'E_CORE_WARNING'; |
||||
case E_COMPILE_ERROR: // 64 // |
||||
return 'E_COMPILE_ERROR'; |
||||
case E_COMPILE_WARNING: // 128 // |
||||
return 'E_COMPILE_WARNING'; |
||||
case E_USER_ERROR: // 256 // |
||||
return 'E_USER_ERROR'; |
||||
case E_USER_WARNING: // 512 // |
||||
return 'E_USER_WARNING'; |
||||
case E_USER_NOTICE: // 1024 // |
||||
return 'E_USER_NOTICE'; |
||||
case E_STRICT: // 2048 // |
||||
return 'E_STRICT'; |
||||
case E_RECOVERABLE_ERROR: // 4096 // |
||||
return 'E_RECOVERABLE_ERROR'; |
||||
case E_DEPRECATED: // 8192 // |
||||
return 'E_DEPRECATED'; |
||||
case E_USER_DEPRECATED: // 16384 // |
||||
return 'E_USER_DEPRECATED'; |
||||
} |
||||
return ""; |
||||
} |
||||
} |
@ -0,0 +1,20 @@ |
||||
#!/bin/bash |
||||
if [ ! -d "$1" ] && [ ! -f "$1" ]; then |
||||
echo "file $1 not exists" |
||||
exit |
||||
fi |
||||
filename=$1 |
||||
|
||||
comment="add file" |
||||
if [[ $2 != "" ]]; then |
||||
comment=$2 |
||||
fi |
||||
|
||||
echo "start update..." |
||||
git pull |
||||
echo "start add new file..." |
||||
git add $filename |
||||
echo "start commit..." |
||||
git commit -m "$comment" $filename |
||||
git push -u origin master |
||||
echo "git commit complete..." |
@ -0,0 +1,129 @@ |
||||
|
||||
_-o#&&*''''?d:>b\_ |
||||
_o/"`'' '',, dMF9MMMMMHo_ |
||||
.o&#' `"MbHMMMMMMMMMMMHo. |
||||
.o"" ' vodM*$&&HMMMMMMMMMM?. |
||||
,' $M&ood,~'`(&##MMMMMMH\ |
||||
/ ,MMMMMMM#b?#bobMMMMHMMML |
||||
& ?MMMMMMMMMMMMMMMMM7MMM$R*Hk |
||||
?$. :MMMMMMMMMMMMMMMMMMM/HMMM|`*L |
||||
| |MMMMMMMMMMMMMMMMMMMMbMH' T, |
||||
$H#: `*MMMMMMMMMMMMMMMMMMMMb#]' `? |
||||
]MMH# ""*""""*#MMMMMMMMMMMMM' - |
||||
MMMMMb_ |MMMMMMMMMMMP' : |
||||
HMMMMMMMHo `MMMMMMMMMT . |
||||
?MMMMMMMMP 9MMMMMMMM] - |
||||
-?MMMMMMM |MMMMMMMMM?,d- ' {Name} |
||||
:|MMMMMM- `MMMMMMMT .M|. : {Description} |
||||
.9MMM[ &MMMMM*' `' . {Loaded} |
||||
:9MMk `MMM#" - |
||||
&M] ` .- |
||||
`&. . |
||||
`~, . ./ |
||||
. _ .- |
||||
'`--._,dd###pp=""' |
||||
|
||||
$$$$$AnyShIt$$$$$$ |
||||
|
||||
_v->#H#P? "':o<>\_ |
||||
.,dP` `'' "'-o.+H6&MMMHo_ |
||||
oHMH9' `?&bHMHMMMMMMHo. |
||||
oMP"' ' ooMP*#&HMMMMMMM?. |
||||
,M* - `*MSdob//`^&##MMMH\ |
||||
d*' .,MMMMMMH#o>#ooMMMMMb |
||||
HM- :HMMMMMMMMMMMMMMM&HM[R\ |
||||
d"Z\. 9MMMMMMMMMMMMMMMMM[HMM|: |
||||
-H - MMMMMMMMMMMMMMMMMMMbMP' : |
||||
:??Mb# `9MMMMMMMMMMMMMMMMMMH#! . |
||||
: MMMMH#, "*""""`#HMMMMMMMMMMH - |
||||
||MMMMMM6\. [MMMMMMMMMH' : |
||||
:|MMMMMMMMMMHo `9MMMMMMMM' . |
||||
. HMMMMMMMMMMP' !MMMMMMMM ` |
||||
- `#MMMMMMMMM HMMMMMMM*,/ : |
||||
: ?MMMMMMMF HMMMMMM',P' : {Name} |
||||
. HMMMMR' [MMMMP' ^' - {Description} |
||||
: `HMMMT iMMH' .' {Loaded} |
||||
-.`HMH . |
||||
-:*H . ' |
||||
-`\,, . .- |
||||
' . _ .-` |
||||
'`~\.__,obb#q==~''' |
||||
|
||||
$$$$$AnyShIt$$$$$$ |
||||
|
||||
_ood>H&H&Z?#M#b-\. |
||||
.\HMMMMMR?`\M6b."`' ''``v. |
||||
.. .MMMMMMMMMMHMMM#&. ``~o. |
||||
. ,HMMMMMMMMMMMM*"'-` &b. |
||||
. .MMMMMMMMMMMMH' `"&\ |
||||
- RMMMMM#H##R' 4Mb |
||||
- |7MMM' ?:: `|MMb |
||||
/ HMM__#|`"\>?v.. `MMML |
||||
. `"'#Hd| ` 9MMM: |
||||
- |\,\?HH#bbL `9MMb |
||||
: !MMMMMMMH#b, `""T |
||||
. . ,MMMMMMMMMMMbo. | |
||||
: 4MMMMMMMMMMMMMMMHo | |
||||
: ?MMMMMMMMMMMMMMM? : |
||||
-. `#MMMMMMMMMMMM: .- |
||||
: |MMMMMMMMMM? . |
||||
- JMMMMMMMT' : {Name} |
||||
`. MMMMMMH' - {Description} |
||||
-. |MMM#*` - {Loaded} |
||||
. HMH' . ' |
||||
-. #H:. .- |
||||
` . .\ .- |
||||
'-..-+oodHL_,--/-` |
||||
|
||||
|
||||
$$$$$AnyShIt$$$$$$ |
||||
|
||||
.,:,#&6dHHHb&##o\_ |
||||
.oHHMMMMMMMMMMMMMMMMMH*\,. |
||||
oHMMMMMMMMMMMMMMMMMMMMMMHb:'-. |
||||
.dMMMMMMMMMMMMMMMMMMMMMMMMMH|\/' . |
||||
,&HMMMMMMMMMMMMMMMMMMMMMMM/"&.,d. -. |
||||
dboMMHMMMMMMMMMMMMMMMMMMMMMML `' . |
||||
HMHMMM$Z***MMMMMMMMMMMMMMMMMM|.- . |
||||
dMM]MMMM#' `9MMMH?"`MMMMR'T' _ : |
||||
|MMMbM#'' |MM" ``MMMH. <_ . |
||||
dMMMM#& *&. .?`*" .'&: . |
||||
MMMMMH- `' -v/H .dD "' ' : |
||||
MMMM* `*M: 4MM*::-!v,_ : |
||||
MMMM `*?::" "'``"?9Mb::. : |
||||
&MMM, `"'"'|"._ "?`| - : |
||||
`MMM].H ,#dM[_H ..: |
||||
9MMi`M: . .ooHMMMMMMM, .. |
||||
9Mb `- 1MMMMMMMMMM| : {Name} |
||||
?M |MM#*#MMMM* . {Description} |
||||
-. ` |#"' ,' {Loaded} |
||||
. -" v` |
||||
-. .- |
||||
- . . ` |
||||
'-*#d#HHMMMMHH#"-' |
||||
|
||||
$$$$$AnyShIt$$$$$$ |
||||
|
||||
.-:?,Z?:&$dHH##b\_ |
||||
,:bqRMMMMMMMMMMMMMMMMMHo. |
||||
.?HHHMMMMMMMMMMMMMMMMMMMMMMMHo. |
||||
-o/*M9MMMMMMMMMMMMMMMMMMMMMMMMMMMv |
||||
.:H\b\'|?#HHMMMMMMMMMMMMMMMMMMMMMM6?Z\ |
||||
.?MMMHbdbbodMMMMHMMMMMMMMMMMMMMMMMMMM\': |
||||
:MMMMMMMMMMM7MMMMb?6P**#MMMMMMMMMMMMMMM_ : |
||||
\MMMMMMMMMMMMb^MMMMMM? `*MMMM*"`MMMR<' . - |
||||
.1MMMMMMMMMMMMMb]M#"" 9MR' `?MMb \. : |
||||
-MMMMMMMMMMMMMMMH##|` *&. |`*' .\ . |
||||
-?""*MMMMMMMMMMMMM' ' |?b ,]" : |
||||
: MMMMMMMMMMH' `M_|M]r\? |
||||
. `MMMMMMMMM' `$_:`'"H |
||||
- TMMMMMMMM, '"``:: |
||||
: [MMMMMMMM| oH| .#M- |
||||
: `9MMMMMM' .MP . ,oMMT |
||||
. HMMMMP' `' ,MMMP {Name} |
||||
- `MMH' HH9* {Description} |
||||
'. ` ` .' {Loaded} |
||||
- . ' |
||||
` . - .- |
||||
` . .- |
||||
' -==pHMMH##HH#""" |
@ -0,0 +1,49 @@ |
||||
<?php |
||||
ini_set("memory_limit", "10240M"); |
||||
require_once __DIR__ . '/../autoloader.php'; |
||||
use phpspider\core\requests; |
||||
use phpspider\core\selector; |
||||
|
||||
/* Do NOT delete this comment */ |
||||
/* 不要删除这段注释 */ |
||||
|
||||
hacked_emails::random_banner(); |
||||
exit; |
||||
class hacked_emails |
||||
{ |
||||
// Colors |
||||
// green - yellow - blue - red - white - magenta - cyan - reset |
||||
public static $color_g = "\033[92m"; |
||||
public static $color_y = "\033[93m"; |
||||
public static $color_b = "\033[94m"; |
||||
public static $color_r = "\033[91m"; |
||||
public static $color_w = "\033[0m"; |
||||
public static $color_m = "\x1b[35m"; |
||||
public static $color_c = "\x1b[36m"; |
||||
public static $end = "\x1b[39m"; |
||||
public static $bold = "\033[1m"; |
||||
|
||||
public static function random_banner() |
||||
{ |
||||
$banners = file_get_contents("banners.txt"); |
||||
$banners = explode('$$$$$AnyShIt$$$$$$', $banners); |
||||
$banner = $banners[count($banners)-1]; |
||||
$banner_to_print = self::$color_g; |
||||
$banner_to_print .= $banner; |
||||
$banner_to_print .= self::$end; |
||||
|
||||
$name = self::$color_b."Hacked Emails By ".self::$bold."@seatle -".self::$color_m." V0.1".self::$color_g; |
||||
$banner_to_print = str_replace("{Name}", $name, $banner_to_print); |
||||
$description = self::$color_c."Know the dangers of email credentials reuse attacks.".self::$color_g; |
||||
$banner_to_print = str_replace("{Description}", $description, $banner_to_print); |
||||
$loaded = self::$color_b."Loaded ".self::$color_y."14".self::$color_b." website.".self::$color_g; |
||||
$banner_to_print = str_replace("{Loaded}", $loaded, $banner_to_print); |
||||
echo $banner_to_print; |
||||
} |
||||
} |
||||
|
||||
$html = requests::get('http://www.qiushibaike.com/article/118914171'); |
||||
//echo $html; |
||||
//exit; |
||||
$data = selector::select($html, "div.author", "css"); |
||||
echo $data; |
@ -0,0 +1,425 @@ |
||||
<?php |
||||
/** |
||||
* Worker多进程操作类 |
||||
* |
||||
* Licensed under The MIT License |
||||
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
* Redistributions of files must retain the above copyright notice. |
||||
* |
||||
* @author seatle<seatle@foxmail.com> |
||||
* @copyright seatle<seatle@foxmail.com> |
||||
* @link http://www.epooll.com/ |
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
*/ |
||||
|
||||
class cls_curl |
||||
{ |
||||
protected static $timeout = 10; |
||||
protected static $ch = null; |
||||
protected static $useragent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36'; |
||||
protected static $http_raw = false; |
||||
protected static $cookie = null; |
||||
protected static $cookie_jar = null; |
||||
protected static $cookie_file = null; |
||||
protected static $referer = null; |
||||
protected static $ip = null; |
||||
protected static $proxy = null; |
||||
protected static $headers = array(); |
||||
protected static $hosts = array(); |
||||
protected static $gzip = false; |
||||
protected static $info = array(); |
||||
|
||||
/** |
||||
* set timeout |
||||
* |
||||
* @param init $timeout |
||||
* @return |
||||
*/ |
||||
public static function set_timeout($timeout) |
||||
{ |
||||
self::$timeout = $timeout; |
||||
} |
||||
|
||||
/** |
||||
* 设置代理 |
||||
* |
||||
* @param mixed $proxy |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public static function set_proxy($proxy) |
||||
{ |
||||
self::$proxy = $proxy; |
||||
} |
||||
|
||||
/** |
||||
* set referer |
||||
* |
||||
*/ |
||||
public static function set_referer($referer) |
||||
{ |
||||
self::$referer = $referer; |
||||
} |
||||
|
||||
/** |
||||
* 设置 user_agent |
||||
* |
||||
* @param string $useragent |
||||
* @return void |
||||
*/ |
||||
public static function set_useragent($useragent) |
||||
{ |
||||
self::$useragent = $useragent; |
||||
} |
||||
|
||||
/** |
||||
* 设置COOKIE |
||||
* |
||||
* @param string $cookie |
||||
* @return void |
||||
*/ |
||||
public static function set_cookie($cookie) |
||||
{ |
||||
self::$cookie = $cookie; |
||||
} |
||||
|
||||
/** |
||||
* 设置COOKIE JAR |
||||
* |
||||
* @param string $cookie_jar |
||||
* @return void |
||||
*/ |
||||
public static function set_cookie_jar($cookie_jar) |
||||
{ |
||||
self::$cookie_jar = $cookie_jar; |
||||
} |
||||
|
||||
/** |
||||
* 设置COOKIE FILE |
||||
* |
||||
* @param string $cookie_file |
||||
* @return void |
||||
*/ |
||||
public static function set_cookie_file($cookie_file) |
||||
{ |
||||
self::$cookie_file = $cookie_file; |
||||
} |
||||
|
||||
/** |
||||
* 获取内容的时候是不是连header也一起获取 |
||||
* |
||||
* @param mixed $http_raw |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public static function set_http_raw($http_raw) |
||||
{ |
||||
self::$http_raw = $http_raw; |
||||
} |
||||
|
||||
/** |
||||
* 设置IP |
||||
* |
||||
* @param string $ip |
||||
* @return void |
||||
*/ |
||||
public static function set_ip($ip) |
||||
{ |
||||
self::$ip = $ip; |
||||
} |
||||
|
||||
/** |
||||
* 设置Headers |
||||
* |
||||
* @param string $headers |
||||
* @return void |
||||
*/ |
||||
public static function set_headers($headers) |
||||
{ |
||||
self::$headers = $headers; |
||||
} |
||||
|
||||
/** |
||||
* 设置Hosts |
||||
* |
||||
* @param string $hosts |
||||
* @return void |
||||
*/ |
||||
public static function set_hosts($hosts) |
||||
{ |
||||
self::$hosts = $hosts; |
||||
} |
||||
|
||||
/** |
||||
* 设置Gzip |
||||
* |
||||
* @param string $hosts |
||||
* @return void |
||||
*/ |
||||
public static function set_gzip($gzip) |
||||
{ |
||||
self::$gzip = $gzip; |
||||
} |
||||
|
||||
/** |
||||
* 初始化 CURL |
||||
* |
||||
*/ |
||||
public static function init() |
||||
{ |
||||
//if (empty ( self::$ch )) |
||||
if (!is_resource ( self::$ch )) |
||||
{ |
||||
self::$ch = curl_init (); |
||||
curl_setopt( self::$ch, CURLOPT_RETURNTRANSFER, true ); |
||||
curl_setopt( self::$ch, CURLOPT_CONNECTTIMEOUT, self::$timeout ); |
||||
curl_setopt( self::$ch, CURLOPT_HEADER, false ); |
||||
curl_setopt( self::$ch, CURLOPT_USERAGENT, self::$useragent ); |
||||
curl_setopt( self::$ch, CURLOPT_TIMEOUT, self::$timeout + 5); |
||||
// 在多线程处理场景下使用超时选项时,会忽略signals对应的处理函数,但是无耐的是还有小概率的crash情况发生 |
||||
curl_setopt( self::$ch, CURLOPT_NOSIGNAL, true); |
||||
} |
||||
return self::$ch; |
||||
} |
||||
|
||||
/** |
||||
* get |
||||
* |
||||
* |
||||
*/ |
||||
public static function get($url, $fields = array()) |
||||
{ |
||||
self::init (); |
||||
return self::http_request($url, 'get', $fields); |
||||
} |
||||
|
||||
/** |
||||
* $fields 有三种类型:1、数组;2、http query;3、json |
||||
* 1、array('name'=>'yangzetao') 2、http_build_query(array('name'=>'yangzetao')) 3、json_encode(array('name'=>'yangzetao')) |
||||
* 前两种是普通的post,可以用$_POST方式获取 |
||||
* 第三种是post stream( json rpc,其实就是webservice ),虽然是post方式,但是只能用流方式 http://input 后者 $HTTP_RAW_POST_DATA 获取 |
||||
* |
||||
* @param mixed $url |
||||
* @param array $fields |
||||
* @param mixed $proxy |
||||
* @static |
||||
* @access public |
||||
* @return void |
||||
*/ |
||||
public static function post($url, $fields = array()) |
||||
{ |
||||
self::init (); |
||||
return self::http_request($url, 'post', $fields); |
||||
} |
||||
|
||||
public static function http_request($url, $type = 'get', $fields) |
||||
{ |
||||
// 如果是 get 方式,直接拼凑一个 url 出来 |
||||
if (strtolower($type) == 'get' && !empty($fields)) |
||||
{ |
||||
$url = $url . (strpos($url,"?")===false ? "?" : "&") . http_build_query($fields); |
||||
} |
||||
|
||||
// 随机绑定 hosts,做负载均衡 |
||||
if (self::$hosts) |
||||
{ |
||||
$parse_url = parse_url($url); |
||||
$host = $parse_url['host']; |
||||
$key = rand(0, count(self::$hosts)-1); |
||||
$ip = self::$hosts[$key]; |
||||
$url = str_replace($host, $ip, $url); |
||||
self::$headers = array_merge( array('Host:'.$host), self::$headers ); |
||||
} |
||||
curl_setopt( self::$ch, CURLOPT_URL, $url ); |
||||
// 如果是 post 方式 |
||||
if (strtolower($type) == 'post') |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_POST, true ); |
||||
curl_setopt( self::$ch, CURLOPT_POSTFIELDS, $fields ); |
||||
} |
||||
if (self::$useragent) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_USERAGENT, self::$useragent ); |
||||
} |
||||
if (self::$cookie) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_COOKIE, self::$cookie ); |
||||
} |
||||
if (self::$cookie_jar) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_COOKIEJAR, self::$cookie_jar ); |
||||
} |
||||
if (self::$cookie_file) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_COOKIEFILE, self::$cookie_file ); |
||||
} |
||||
if (self::$referer) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_REFERER, self::$referer ); |
||||
} |
||||
if (self::$ip) |
||||
{ |
||||
self::$headers = array_merge( array('CLIENT-IP:'.self::$ip, 'X-FORWARDED-FOR:'.self::$ip), self::$headers ); |
||||
} |
||||
if (self::$headers) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_HTTPHEADER, self::$headers ); |
||||
} |
||||
if (self::$gzip) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_ENCODING, 'gzip' ); |
||||
} |
||||
if (self::$proxy) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_PROXY, self::$proxy ); |
||||
} |
||||
if (self::$http_raw) |
||||
{ |
||||
curl_setopt( self::$ch, CURLOPT_HEADER, true ); |
||||
} |
||||
|
||||
$data = curl_exec ( self::$ch ); |
||||
self::$info = curl_getinfo(self::$ch); |
||||
if ($data === false) |
||||
{ |
||||
//echo date("Y-m-d H:i:s"), ' Curl error: ' . curl_error( self::$ch ), "\n"; |
||||
} |
||||
|
||||
// 关闭句柄 |
||||
curl_close( self::$ch ); |
||||
//$data = substr($data, 10); |
||||
//$data = gzinflate($data); |
||||
return $data; |
||||
} |
||||
|
||||
public static function get_info() |
||||
{ |
||||
return self::$info; |
||||
} |
||||
|
||||
public static function get_http_code() |
||||
{ |
||||
return self::$info['http_code']; |
||||
} |
||||
} |
||||
|
||||
function classic_curl($urls, $delay) |
||||
{ |
||||
$queue = curl_multi_init(); |
||||
$map = array(); |
||||
|
||||
foreach ($urls as $url) |
||||
{ |
||||
// create cURL resources |
||||
$ch = curl_init(); |
||||
|
||||
// 设置 URL 和 其他参数 |
||||
curl_setopt($ch, CURLOPT_URL, $url); |
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 1); |
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
||||
curl_setopt($ch, CURLOPT_HEADER, 0); |
||||
curl_setopt($ch, CURLOPT_NOSIGNAL, true); |
||||
|
||||
// 把当前 curl resources 加入到 curl_multi_init 队列 |
||||
curl_multi_add_handle($queue, $ch); |
||||
$map[$url] = $ch; |
||||
} |
||||
|
||||
$active = null; |
||||
|
||||
// execute the handles |
||||
do { |
||||
$mrc = curl_multi_exec($queue, $active); |
||||
} while ($mrc == CURLM_CALL_MULTI_PERFORM); |
||||
|
||||
while ($active > 0 && $mrc == CURLM_OK) { |
||||
while (curl_multi_exec($queue, $active) === CURLM_CALL_MULTI_PERFORM); |
||||
// 这里 curl_multi_select 一直返回 -1,所以这里就死循环了,CPU就100%了 |
||||
if (curl_multi_select($queue, 0.5) != -1) |
||||
{ |
||||
do { |
||||
$mrc = curl_multi_exec($queue, $active); |
||||
} while ($mrc == CURLM_CALL_MULTI_PERFORM); |
||||
} |
||||
} |
||||
|
||||
$responses = array(); |
||||
foreach ($map as $url=>$ch) { |
||||
//$responses[$url] = callback(curl_multi_getcontent($ch), $delay); |
||||
$responses[$url] = callback(curl_multi_getcontent($ch), $delay, $url); |
||||
curl_multi_remove_handle($queue, $ch); |
||||
curl_close($ch); |
||||
} |
||||
|
||||
curl_multi_close($queue); |
||||
return $responses; |
||||
} |
||||
|
||||
function rolling_curl($urls, $delay) |
||||
{ |
||||
$queue = curl_multi_init(); |
||||
$map = array(); |
||||
|
||||
foreach ($urls as $url) { |
||||
$ch = curl_init(); |
||||
|
||||
curl_setopt($ch, CURLOPT_URL, $url); |
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 10); |
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); |
||||
curl_setopt($ch, CURLOPT_HEADER, 0); |
||||
curl_setopt($ch, CURLOPT_NOSIGNAL, true); |
||||
$cookie = '_za=36643642-e546-4d60-a771-8af8dcfbd001; q_c1=a57a2b9f10964f909b8d8969febf3ab2|1437705596000|1437705596000; _xsrf=f0304fba4e44e1d008ec308d59bab029; cap_id="YWY1YmRmODlmZGVmNDc3MWJlZGFkZDg3M2E0M2Q5YjM=|1437705596|963518c454bb6f10d96775021c098c84e1e46f5a"; z_c0="QUFCQVgtRWZBQUFYQUFBQVlRSlZUVjR6NEZVUTgtRkdjTVc5UDMwZXRJZFdWZ2JaOWctNVhnPT0=|1438164574|aed6ef3707f246a7b64da4f1e8c089395d77ff2b"; __utma=51854390.1105113342.1437990174.1438160686.1438164116.10; __utmc=51854390; __utmz=51854390.1438134939.8.5.utmcsr=zhihu.com|utmccn=(referral)|utmcmd=referral|utmcct=/people/yangzetao; __utmv=51854390.100-1|2=registration_date=20131030=1^3=entry_date=20131030=1'; |
||||
curl_setopt($ch, CURLOPT_COOKIE, $cookie); |
||||
$useragent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36'; |
||||
curl_setopt( $ch, CURLOPT_USERAGENT, $useragent ); |
||||
curl_setopt($ch, CURLOPT_ENCODING, 'gzip'); |
||||
|
||||
curl_multi_add_handle($queue, $ch); |
||||
$map[(string) $ch] = $url; |
||||
} |
||||
|
||||
$responses = array(); |
||||
do { |
||||
while (($code = curl_multi_exec($queue, $active)) == CURLM_CALL_MULTI_PERFORM) ; |
||||
|
||||
if ($code != CURLM_OK) { break; } |
||||
|
||||
// a request was just completed -- find out which one |
||||
while ($done = curl_multi_info_read($queue)) { |
||||
|
||||
// get the info and content returned on the request |
||||
$info = curl_getinfo($done['handle']); |
||||
$error = curl_error($done['handle']); |
||||
$results = callback(curl_multi_getcontent($done['handle']), $delay, $map[(string) $done['handle']]); |
||||
$responses[$map[(string) $done['handle']]] = compact('info', 'error', 'results'); |
||||
|
||||
// remove the curl handle that just completed |
||||
curl_multi_remove_handle($queue, $done['handle']); |
||||
curl_close($done['handle']); |
||||
} |
||||
|
||||
// Block for data in / output; error handling is done by curl_multi_exec |
||||
if ($active > 0) { |
||||
curl_multi_select($queue, 0.5); |
||||
} |
||||
|
||||
} while ($active); |
||||
|
||||
curl_multi_close($queue); |
||||
return $responses; |
||||
} |
||||
|
||||
function callback($data, $delay, $url) { |
||||
//echo $data; |
||||
//echo date("Y-m-d H:i:s", time()) . " --- " . $url . "\n"; |
||||
if (!empty($data)) |
||||
{ |
||||
file_put_contents("./html2/".md5($url).".html", $data); |
||||
} |
||||
// usleep模拟现实中比较负责的数据处理逻辑(如提取, 分词, 写入文件或数据库等) |
||||
//usleep(1); |
||||
//return compact('data', 'matches'); |
||||
} |
||||
|
@ -0,0 +1,248 @@ |
||||
<?php |
||||
class cls_query |
||||
{ |
||||
private static $content; |
||||
public static $debug = false; |
||||
|
||||
public static function init($content) |
||||
{ |
||||
self::$content = $content; |
||||
} |
||||
|
||||
public static function query($query, $attr = "html") |
||||
{ |
||||
$nodes = self::get_nodes($query); |
||||
$datas = self::get_datas($nodes, $attr); |
||||
return $datas; |
||||
} |
||||
|
||||
protected static function is_char($char) { |
||||
return extension_loaded('mbstring') ? mb_eregi('\w', $char) : preg_match('@\w@', $char); |
||||
} |
||||
|
||||
/** |
||||
* 从xpath中得到节点 |
||||
* |
||||
* @param mixed $xpath |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2015-08-08 15:52 |
||||
*/ |
||||
private static function get_nodes($query) |
||||
{ |
||||
// 把一到多个空格 替换成 一个空格 |
||||
// 把 > 和 ~ 符号两边的空格去掉,因为没有用这两个符号,所以这里可以不这么做 |
||||
// ul>li.className |
||||
$query = trim( |
||||
preg_replace('@\s+@', ' ', |
||||
preg_replace('@\s*(>|\\+|~)\s*@', '\\1', $query) |
||||
) |
||||
); |
||||
|
||||
$nodes = array(); |
||||
if (! $query) |
||||
{ |
||||
return $nodes; |
||||
} |
||||
|
||||
$query_arr = explode(" ", $query); |
||||
foreach ($query_arr as $k=>$v) |
||||
{ |
||||
$path = $k == 0 ? $v : $path.' '.$v; |
||||
$node = array("path"=>(string)$path, "name"=>"", "id"=>"", "class"=>"", "other"=>array()); |
||||
// 如果存在内容选择器 |
||||
if (preg_match('@(.*?)\[(.*?)=[\'|"](.*?)[\'|"]\]@', $v, $matches) && !empty($matches[2]) && !empty($matches[3])) |
||||
{ |
||||
// 把选择器过滤掉 [rel='topic'] |
||||
$v = $matches[1]; |
||||
$node['other'] = array( |
||||
'key'=>$matches[2], |
||||
'val'=>$matches[3], |
||||
); |
||||
} |
||||
|
||||
// 如果存在 id |
||||
$id_arr = explode("#", $v); |
||||
$class_arr = explode(".", $v); |
||||
if (count($id_arr) === 2) |
||||
{ |
||||
$node['name'] = $id_arr[0]; |
||||
$node['id'] = $id_arr[1]; |
||||
} |
||||
// 如果存在 class |
||||
elseif (count($class_arr) === 2) |
||||
{ |
||||
$node['name'] = $class_arr[0]; |
||||
$node['class'] = $class_arr[1]; |
||||
} |
||||
// 如果没有样式 |
||||
else |
||||
{ |
||||
$node['name'] = $v; |
||||
} |
||||
$nodes[] = $node; |
||||
} |
||||
//print_r($nodes); |
||||
//exit; |
||||
return $nodes; |
||||
} |
||||
|
||||
public static function get_datas($nodes, $attr = "html") |
||||
{ |
||||
if (empty(self::$content)) |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
$node_datas = array(); |
||||
$count = count($nodes); |
||||
// 循环所有节点 |
||||
foreach ($nodes as $i=>$node) |
||||
{ |
||||
$is_last = $count == $i+1 ? true : false; |
||||
// 第一次 |
||||
if ($i == 0) |
||||
{ |
||||
$datas = array(); |
||||
$datas = self::get_node_datas($node, self::$content, $attr, $is_last); |
||||
// 如果第一次都取不到数据,直接跳出循环 |
||||
if(!$datas) |
||||
{ |
||||
break; |
||||
} |
||||
$node_datas[$nodes[$i]['path']] = $datas; |
||||
} |
||||
else |
||||
{ |
||||
$datas = array(); |
||||
// 循环上一个节点的数组 |
||||
foreach ($node_datas[$nodes[$i-1]['path']] as $v) |
||||
{ |
||||
$datas = array_merge( $datas, self::get_node_datas($node, trim($v), $attr, $is_last) ); |
||||
} |
||||
$node_datas[$nodes[$i]['path']] = $datas; |
||||
// 删除上一个节点,防止内存溢出,或者缓存到本地,再次使用?! |
||||
unset($node_datas[$nodes[$i-1]['path']]); |
||||
} |
||||
} |
||||
//print_r($datas);exit; |
||||
// 从数组中弹出最后一个元素 |
||||
$node_datas = array_pop($node_datas); |
||||
//print_r($node_datas); |
||||
//exit; |
||||
return $node_datas; |
||||
} |
||||
|
||||
/** |
||||
* 从节点中获取内容 |
||||
* $regex = '@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i'; |
||||
* |
||||
* @param mixed $node |
||||
* @param mixed $content |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2015-08-08 15:52 |
||||
*/ |
||||
private static function get_node_datas($node, $content, $attr = "html", $is_last = false) |
||||
{ |
||||
$node_datas = $datas = array(); |
||||
|
||||
if (!empty($node['id'])) |
||||
{ |
||||
if ($node['name']) |
||||
$regex = '@<'.$node['name'].'[^>]+id\\s*=\\s*["|\']+?'.$node['id'].'\\s*[^>]+?>(.*?)</'.$node['name'].'>@is'; |
||||
else |
||||
$regex = '@id\\s*=\\s*["|\']+?'.$node['id'].'\\s*[^>]+?>(.*?)<@is'; |
||||
} |
||||
elseif (!empty($node['class'])) |
||||
{ |
||||
if ($node['name']) |
||||
$regex = '@<'.$node['name'].'[^>]+class\\s*=\\s*["|\']+?'.$node['class'].'\\s*[^>]+?>(.*?)</'.$node['name'].'>@is'; |
||||
else |
||||
$regex = '@class\\s*=\\s*["|\']+?'.$node['class'].'\\s*[^>]+?>(.*?)<@is'; |
||||
} |
||||
else |
||||
{ |
||||
// 这里为是么是*,0次到多次,因为有可能是 <li> |
||||
$regex = '@<'.$node['name'].'[^>]*?>(.*?)</'.$node['name'].'>@is'; |
||||
} |
||||
self::log("regex --- " . $regex);; |
||||
preg_match_all($regex, $content, $matches); |
||||
$all_datas = empty($matches[0]) ? array() : $matches[0]; |
||||
$html_datas = empty($matches[1]) ? array() : $matches[1]; |
||||
|
||||
// 过滤掉选择器对不上的 |
||||
foreach ($all_datas as $i=>$data) |
||||
{ |
||||
// 如果有设置其他选择器,验证一下选择器 |
||||
if (!empty($node['other'])) |
||||
{ |
||||
$regex = '@'.$node['other']['key'].'=[\'|"]'.$node['other']['val'].'[\'|"]@is'; |
||||
self::log("regex other --- " . $regex); |
||||
// 过滤器对不上的,跳过 |
||||
if (!preg_match($regex, $data, $matches)) |
||||
{ |
||||
continue; |
||||
} |
||||
} |
||||
// 获取节点的html内容 |
||||
if ($attr != "html" && $is_last) |
||||
{ |
||||
$regex = '@'.$attr.'=[\'|"](.*?)[\'|"]@is'; |
||||
preg_match($regex, $data, $matches); |
||||
$node_datas[] = empty($matches[1]) ? '' : trim($matches[1]); |
||||
} |
||||
// 获取节点属性名的值 |
||||
else |
||||
{ |
||||
$node_datas[] = trim($html_datas[$i]); |
||||
} |
||||
} |
||||
//echo " 11111 ========================================= \n"; |
||||
//print_r($node_datas); |
||||
//echo " 22222 ========================================= \n\n\n"; |
||||
return $node_datas; |
||||
} |
||||
|
||||
/** |
||||
* 记录日志 |
||||
* @param string $msg |
||||
* @return void |
||||
*/ |
||||
private static function log($msg) |
||||
{ |
||||
$msg = "[".date("Y-m-d H:i:s")."] " . $msg . "\n"; |
||||
if (self::$debug) |
||||
{ |
||||
echo $msg; |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
//$xpath = "ul.top-nav-dropdown li"; |
||||
//$xpath = "i.zg-icon"; |
||||
//print_r($nodes); |
||||
//exit; |
||||
// [^>]+ 不是>的字符重复一次到多次, ? 表示不贪婪 |
||||
// \s 表示空白字符 |
||||
// * 表示0次或者多次 |
||||
// + 表示1次或者多次 |
||||
// |
||||
// 后向引用,表示表达式中,从左往右数,第一个左括号对应的括号内的内容。 |
||||
// \\0 表示整个表达式 |
||||
// \\1表示第1个表达式 |
||||
// \\2表示第2个表达式 |
||||
// $regex = '@<meta[^>]+http-equiv\\s*=\\s*(["|\'])Content-Type\\1([^>]+?)>@i'; |
||||
//preg_match_all($regex, $content, $matches); |
||||
//print_r($matches); |
||||
//exit; |
||||
|
||||
// 用法 |
||||
//$content = file_get_contents("./test.html"); |
||||
//$query = "ul#top-nav-profile-dropdown li a"; |
||||
//$query = "div#zh-profile-following-topic a.link[href='/topic/19550937']"; |
||||
//cls_query::init($content); |
||||
//$list = cls_query::query($query, "href"); |
||||
//print_r($list); |
||||
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,121 @@ |
||||
<?php |
||||
/** |
||||
* redis 客户端 |
||||
* redis的协议可参考这个文章http://redis.cn/topics/protocol.html |
||||
* |
||||
* @version 2.7.0 |
||||
* @copyright 1997-2018 The PHP Group |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2018-01-03 |
||||
*/ |
||||
class cls_redis_client |
||||
{ |
||||
private $redis_socket = false; |
||||
//private $command = ''; |
||||
|
||||
public function __construct($host='127.0.0.1', $port=6379, $timeout = 3) |
||||
{ |
||||
$this->redis_socket = stream_socket_client("tcp://".$host.":".$port, $errno, $errstr, $timeout); |
||||
if ( !$this->redis_socket ) |
||||
{ |
||||
throw new Exception("{$errno} - {$errstr}"); |
||||
} |
||||
} |
||||
|
||||
public function __destruct() |
||||
{ |
||||
fclose($this->redis_socket); |
||||
} |
||||
|
||||
public function __call($name, $args) |
||||
{ |
||||
$crlf = "\r\n"; |
||||
array_unshift($args, $name); |
||||
$command = '*' . count($args) . $crlf; |
||||
foreach ($args as $arg) |
||||
{ |
||||
$command .= '$' . strlen($arg) . $crlf . $arg . $crlf; |
||||
} |
||||
//echo $command."\n"; |
||||
$fwrite = fwrite($this->redis_socket, $command); |
||||
if ($fwrite === FALSE || $fwrite <= 0) |
||||
{ |
||||
throw new Exception('Failed to write entire command to stream'); |
||||
} |
||||
return $this->read_response(); |
||||
} |
||||
|
||||
private function read_response() |
||||
{ |
||||
$reply = trim(fgets($this->redis_socket, 1024)); |
||||
switch (substr($reply, 0, 1)) |
||||
{ |
||||
case '-': |
||||
throw new Exception(trim(substr($reply, 1))); |
||||
break; |
||||
case '+': |
||||
$response = substr(trim($reply), 1); |
||||
if ($response === 'OK') |
||||
{ |
||||
$response = TRUE; |
||||
} |
||||
break; |
||||
case '$': |
||||
$response = NULL; |
||||
if ($reply == '$-1') |
||||
{ |
||||
break; |
||||
} |
||||
$read = 0; |
||||
$size = intval(substr($reply, 1)); |
||||
if ($size > 0) |
||||
{ |
||||
do |
||||
{ |
||||
$block_size = ($size - $read) > 1024 ? 1024 : ($size - $read); |
||||
$r = fread($this->redis_socket, $block_size); |
||||
if ($r === FALSE) |
||||
{ |
||||
throw new Exception('Failed to read response from stream'); |
||||
} |
||||
else |
||||
{ |
||||
$read += strlen($r); |
||||
$response .= $r; |
||||
} |
||||
} |
||||
while ($read < $size); |
||||
} |
||||
fread($this->redis_socket, 2); /* discard crlf */ |
||||
break; |
||||
/* Multi-bulk reply */ |
||||
case '*': |
||||
$count = intval(substr($reply, 1)); |
||||
if ($count == '-1') |
||||
{ |
||||
return NULL; |
||||
} |
||||
$response = array(); |
||||
for ($i = 0; $i < $count; $i++) |
||||
{ |
||||
$response[] = $this->read_response(); |
||||
} |
||||
break; |
||||
/* Integer reply */ |
||||
case ':': |
||||
$response = intval(substr(trim($reply), 1)); |
||||
break; |
||||
default: |
||||
throw new RedisException("Unknown response: {$reply}"); |
||||
break; |
||||
} |
||||
return $response; |
||||
} |
||||
} |
||||
|
||||
|
||||
//$redis = new cls_redis_client(); |
||||
//var_dump($redis->auth("foobared")); |
||||
//var_dump($redis->set("name",'abc')); |
||||
//var_dump($redis->get("name")); |
||||
|
@ -0,0 +1,179 @@ |
||||
<?php |
||||
ini_set("memory_limit", "128M"); |
||||
/** |
||||
* redis 服务端 |
||||
* 多进程阻塞式 |
||||
* redis-benchmark -h 127.0.0.1 -p 11211 -t set -n 80000 -q |
||||
* |
||||
* @version 2.7.0 |
||||
* @copyright 1997-2018 The PHP Group |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2018-01-03 |
||||
*/ |
||||
class cls_redis_server |
||||
{ |
||||
private $socket = false; |
||||
private $process_num = 3; |
||||
public $redis_kv_data = array(); |
||||
public $onMessage = null; |
||||
|
||||
public function __construct($host="0.0.0.0", $port=6379) |
||||
{ |
||||
$this->socket = stream_socket_server("tcp://".$host.":".$port,$errno, $errstr); |
||||
if (!$this->socket) die($errstr."--".$errno); |
||||
echo "listen $host $port \r\n"; |
||||
} |
||||
|
||||
private function parse_resp(&$conn) |
||||
{ |
||||
// 读取一行,遇到 \r\n 为一行 |
||||
$line = fgets($conn); |
||||
if($line === '' || $line === false) |
||||
{ |
||||
return null; |
||||
} |
||||
// 获取第一个字符作为类型 |
||||
$type = $line[0]; |
||||
// 去掉第一个字符,去掉结尾的 \r\n |
||||
$line = mb_substr($line, 1, -2); |
||||
switch ( $type ) |
||||
{ |
||||
case "*": |
||||
// 得到长度 |
||||
$count = (int) $line; |
||||
$data = array(); |
||||
for ($i = 1; $i <= $count; $i++) |
||||
{ |
||||
$data[] = $this->parse_resp($conn); |
||||
} |
||||
return $data; |
||||
case "$": |
||||
if ($line == '-1') |
||||
{ |
||||
return null; |
||||
} |
||||
// 截取的长度要加上 \r\n 两个字符 |
||||
$length = $line + 2; |
||||
$data = ''; |
||||
while ($length > 0) |
||||
{ |
||||
$block = fread($conn, $length); |
||||
if ($length !== strlen($block)) |
||||
{ |
||||
throw new Exception('RECEIVING'); |
||||
} |
||||
$data .= $block; |
||||
$length -= mb_strlen($block); |
||||
} |
||||
return mb_substr($data, 0, -2); |
||||
} |
||||
return $line; |
||||
} |
||||
|
||||
private function start_worker_process() |
||||
{ |
||||
$pid = pcntl_fork(); |
||||
switch ($pid) |
||||
{ |
||||
case -1: |
||||
echo "fork error : {$i} \r\n"; |
||||
exit; |
||||
case 0: |
||||
while ( true ) |
||||
{ |
||||
echo "PID ".posix_getpid()." waiting...\n"; |
||||
// 堵塞等待 |
||||
$conn = stream_socket_accept($this->socket, -1); |
||||
if ( !$conn ) |
||||
{ |
||||
continue; |
||||
} |
||||
//"*3\r\n$3\r\nSET\r\n$5\r\nmykey\r\n$7\r\nmyvalue\r\n" |
||||
while( true ) |
||||
{ |
||||
$arr = $this->parse_resp($conn); |
||||
if ( is_array($arr) ) |
||||
{ |
||||
if ($this->onMessage) |
||||
{ |
||||
call_user_func($this->onMessage, $conn, $arr); |
||||
} |
||||
} |
||||
else if ( $arr ) |
||||
{ |
||||
if ($this->onMessage) |
||||
{ |
||||
call_user_func($this->onMessage, $conn, $arr); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
fclose($conn); |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
default: |
||||
$this->pids[$pid] = $pid; |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public function run() |
||||
{ |
||||
for($i = 1; $i <= $this->process_num; $i++) |
||||
{ |
||||
$this->start_worker_process(); |
||||
} |
||||
|
||||
while( true ) |
||||
{ |
||||
foreach ($this->pids as $i => $pid) |
||||
{ |
||||
if($pid) |
||||
{ |
||||
$res = pcntl_waitpid($pid, $status,WNOHANG); |
||||
|
||||
if ( $res == -1 || $res > 0 ) |
||||
{ |
||||
$this->start_worker_process(); |
||||
unset($this->pids[$pid]); |
||||
} |
||||
} |
||||
} |
||||
sleep(1); |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
$server = new cls_redis_server(); |
||||
$server->onMessage = function($conn, $info) use($server) |
||||
{ |
||||
if ( is_array($info) ) |
||||
{ |
||||
$command = strtoupper($info[0]); |
||||
if ( $command == "SET" ) |
||||
{ |
||||
$key = $info[1]; |
||||
$val = $info[2]; |
||||
$server->redis_kv_data[$key] = $val; |
||||
fwrite($conn, "+OK\r\n"); |
||||
} |
||||
else if ( $command == "GET" ) |
||||
{ |
||||
$key = $info[1]; |
||||
$val = isset($server->redis_kv_data[$key]) ? $server->redis_kv_data[$key] : ''; |
||||
fwrite($conn, "$".strlen($val)."\r\n".$val."\r\n"); |
||||
} |
||||
else |
||||
{ |
||||
fwrite($conn,"+OK\r\n"); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
fwrite($conn,"+OK\r\n"); |
||||
} |
||||
}; |
||||
$server->run(); |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,466 @@ |
||||
<?php |
||||
/** |
||||
* Curl操作类 |
||||
* |
||||
* Licensed under The MIT License |
||||
* For full copyright and license information, please see the MIT-LICENSE.txt |
||||
* Redistributions of files must retain the above copyright notice. |
||||
* |
||||
* @author seatle<seatle@foxmail.com> |
||||
* @copyright seatle<seatle@foxmail.com> |
||||
* @link http://www.epooll.com/ |
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT License |
||||
*/ |
||||
|
||||
class rolling_curl |
||||
{ |
||||
/** |
||||
* @var float |
||||
* |
||||
* 同时运行任务数 |
||||
* 例如:有8个请求,则会被分成两批,第一批5个请求,第二批3个请求 |
||||
* 注意:采集知乎的时候,5个是比较稳定的,7个以上就开始会超时了,多进程就没有这样的问题,因为多进程很少几率会发生并发 |
||||
*/ |
||||
public $window_size = 5; |
||||
|
||||
/** |
||||
* @var float |
||||
* |
||||
* Timeout is the timeout used for curl_multi_select. |
||||
*/ |
||||
private $timeout = 10; |
||||
|
||||
/** |
||||
* @var string|array |
||||
* |
||||
* 应用在每个请求的回调函数 |
||||
*/ |
||||
public $callback; |
||||
|
||||
/** |
||||
* @var array |
||||
* |
||||
* 设置默认的请求参数 |
||||
*/ |
||||
protected $options = array( |
||||
CURLOPT_SSL_VERIFYPEER => 0, |
||||
CURLOPT_RETURNTRANSFER => 1, |
||||
// 注意:TIMEOUT = CONNECTTIMEOUT + 数据获取时间,所以 TIMEOUT 一定要大于 CONNECTTIMEOUT,否则 CONNECTTIMEOUT 设置了就没意义 |
||||
// "Connection timed out after 30001 milliseconds" |
||||
CURLOPT_CONNECTTIMEOUT => 30, |
||||
CURLOPT_TIMEOUT => 60, |
||||
CURLOPT_RETURNTRANSFER => 1, |
||||
CURLOPT_HEADER => 0, |
||||
// 在多线程处理场景下使用超时选项时,会忽略signals对应的处理函数,但是无耐的是还有小概率的crash情况发生 |
||||
CURLOPT_NOSIGNAL => 1, |
||||
CURLOPT_USERAGENT => "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.89 Safari/537.36", |
||||
); |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
private $headers = array(); |
||||
|
||||
/** |
||||
* @var Request[] |
||||
* |
||||
* 请求队列 |
||||
*/ |
||||
private $requests = array(); |
||||
|
||||
/** |
||||
* @var RequestMap[] |
||||
* |
||||
* Maps handles to request indexes |
||||
*/ |
||||
private $requestMap = array(); |
||||
|
||||
public function __construct() |
||||
{ |
||||
} |
||||
|
||||
/** |
||||
* set timeout |
||||
* |
||||
* @param init $timeout |
||||
* @return |
||||
*/ |
||||
public function set_timeout($timeout) |
||||
{ |
||||
$this->options[CURLOPT_TIMEOUT] = $timeout; |
||||
} |
||||
|
||||
/** |
||||
* set proxy |
||||
* |
||||
*/ |
||||
public function set_proxy($proxy) |
||||
{ |
||||
$this->options[CURLOPT_PROXY] = $proxy; |
||||
} |
||||
|
||||
/** |
||||
* set referer |
||||
* |
||||
*/ |
||||
public function set_referer($referer) |
||||
{ |
||||
$this->options[CURLOPT_REFERER] = $referer; |
||||
} |
||||
|
||||
/** |
||||
* 设置 user_agent |
||||
* |
||||
* @param string $useragent |
||||
* @return void |
||||
*/ |
||||
public function set_useragent($useragent) |
||||
{ |
||||
$this->options[CURLOPT_USERAGENT] = $useragent; |
||||
} |
||||
|
||||
/** |
||||
* 设置COOKIE |
||||
* |
||||
* @param string $cookie |
||||
* @return void |
||||
*/ |
||||
public function set_cookie($cookie) |
||||
{ |
||||
$this->options[CURLOPT_COOKIE] = $cookie; |
||||
} |
||||
|
||||
/** |
||||
* 设置COOKIE JAR |
||||
* |
||||
* @param string $cookie_jar |
||||
* @return void |
||||
*/ |
||||
public function set_cookiejar($cookiejar) |
||||
{ |
||||
$this->options[CURLOPT_COOKIEJAR] = $cookiejar; |
||||
} |
||||
|
||||
/** |
||||
* 设置COOKIE FILE |
||||
* |
||||
* @param string $cookie_file |
||||
* @return void |
||||
*/ |
||||
public function set_cookiefile($cookiefile) |
||||
{ |
||||
$this->options[CURLOPT_COOKIEFILE] = $cookiefile; |
||||
} |
||||
|
||||
/** |
||||
* 获取内容的时候是不是连header也一起获取 |
||||
* |
||||
* @param mixed $http_raw |
||||
* @return void |
||||
* @author seatle <seatle@foxmail.com> |
||||
* @created time :2016-09-18 10:17 |
||||
*/ |
||||
public function set_http_raw($http_raw = false) |
||||
{ |
||||
$this->options[CURLOPT_HEADER] = $http_raw; |
||||
} |
||||
|
||||
/** |
||||
* 设置IP |
||||
* |
||||
* @param string $ip |
||||
* @return void |
||||
*/ |
||||
public function set_ip($ip) |
||||
{ |
||||
$headers = array( |
||||
'CLIENT-IP'=>$ip, |
||||
'X-FORWARDED-FOR'=>$ip, |
||||
); |
||||
$this->headers = $this->headers + $headers; |
||||
} |
||||
|
||||
/** |
||||
* 设置Headers |
||||
* |
||||
* @param string $headers |
||||
* @return void |
||||
*/ |
||||
public function set_headers($headers) |
||||
{ |
||||
$this->headers = $this->headers + $headers; |
||||
} |
||||
|
||||
/** |
||||
* 设置Hosts |
||||
* |
||||
* @param string $hosts |
||||
* @return void |
||||
*/ |
||||
public function set_hosts($hosts) |
||||
{ |
||||
$headers = array( |
||||
'Host'=>$hosts, |
||||
); |
||||
$this->headers = $this->headers + $headers; |
||||
} |
||||
|
||||
/** |
||||
* 设置Gzip |
||||
* |
||||
* @param string $hosts |
||||
* @return void |
||||
*/ |
||||
public function set_gzip($gzip) |
||||
{ |
||||
if ($gzip) |
||||
{ |
||||
$this->options[CURLOPT_ENCODING] = 'gzip'; |
||||
} |
||||
} |
||||
|
||||
public function request($url, $method = "GET", $fields = array(), $headers = array(), $options = array()) |
||||
{ |
||||
$this->requests[] = array('url'=>$url,'method'=>$method,'fields'=>$fields,'headers'=>$headers,'options'=>$options); |
||||
return true; |
||||
} |
||||
|
||||
public function get_options($request) |
||||
{ |
||||
$options = $this->options; |
||||
$headers = $this->headers; |
||||
|
||||
if (ini_get('safe_mode') == 'Off' || !ini_get('safe_mode')) |
||||
{ |
||||
$options[CURLOPT_FOLLOWLOCATION] = 1; |
||||
$options[CURLOPT_MAXREDIRS] = 5; |
||||
} |
||||
|
||||
// 如果是 get 方式,直接拼凑一个 url 出来 |
||||
if (strtolower($request['method']) == 'get' && !empty($request['fields'])) |
||||
{ |
||||
$url = $request['url'] . "?" . http_build_query($request['fields']); |
||||
} |
||||
// 如果是 post 方式 |
||||
if (strtolower($request['method']) == 'post') |
||||
{ |
||||
$options[CURLOPT_POST] = 1; |
||||
$options[CURLOPT_POSTFIELDS] = $request['fields']; |
||||
} |
||||
|
||||
// append custom options for this specific request |
||||
if ($request['options']) |
||||
{ |
||||
$options = $request['options'] + $options; |
||||
} |
||||
|
||||
if ($request['headers']) |
||||
{ |
||||
$headers = $request['headers'] + $headers; |
||||
} |
||||
|
||||
// 随机绑定 hosts,做负载均衡 |
||||
//if (self::$hosts) |
||||
//{ |
||||
//$parse_url = parse_url($url); |
||||
//$host = $parse_url['host']; |
||||
//$key = rand(0, count(self::$hosts)-1); |
||||
//$ip = self::$hosts[$key]; |
||||
//$url = str_replace($host, $ip, $url); |
||||
//self::$headers = array_merge( array('Host:'.$host), self::$headers ); |
||||
//} |
||||
|
||||
// header 要这样拼凑 |
||||
$headers_tmp = array(); |
||||
foreach ($headers as $k=>$v) |
||||
{ |
||||
$headers_tmp[] = $k.":".$v; |
||||
} |
||||
$headers = $headers_tmp; |
||||
|
||||
$options[CURLOPT_URL] = $request['url']; |
||||
$options[CURLOPT_HTTPHEADER] = $headers; |
||||
|
||||
return $options; |
||||
} |
||||
|
||||
/** |
||||
* GET 请求 |
||||
* |
||||
* @param string $url |
||||
* @param array $headers |
||||
* @param array $options |
||||
* @return bool |
||||
*/ |
||||
public function get($url, $fields = array(), $headers = array(), $options = array()) |
||||
{ |
||||
return $this->request($url, 'get', $fields, $headers, $options); |
||||
} |
||||
|
||||
/** |
||||
* $fields 有三种类型:1、数组;2、http query;3、json |
||||
* 1、array('name'=>'yangzetao') 2、http_build_query(array('name'=>'yangzetao')) 3、json_encode(array('name'=>'yangzetao')) |
||||
* 前两种是普通的post,可以用$_POST方式获取 |
||||
* 第三种是post stream( json rpc,其实就是webservice ),虽然是post方式,但是只能用流方式 http://input 后者 $HTTP_RAW_POST_DATA 获取 |
||||
* |
||||
* @param string $url |
||||
* @param array $fields |
||||
* @param array $headers |
||||
* @param array $options |
||||
* @return void |
||||
*/ |
||||
public function post($url, $fields = array(), $headers = array(), $options = array()) |
||||
{ |
||||
return $this->request($url, 'post', $fields, $headers, $options); |
||||
} |
||||
|
||||
/** |
||||
* Execute processing |
||||
* |
||||
* @param int $window_size Max number of simultaneous connections |
||||
* @return string|bool |
||||
*/ |
||||
public function execute($window_size = null) |
||||
{ |
||||
$count = sizeof($this->requests); |
||||
if ($count == 0) |
||||
{ |
||||
return false; |
||||
} |
||||
// 只有一个请求 |
||||
elseif ($count == 1) |
||||
{ |
||||
return $this->single_curl(); |
||||
} |
||||
else |
||||
{ |
||||
// 开始 rolling curl,window_size 是最大同时连接数 |
||||
return $this->rolling_curl($window_size); |
||||
} |
||||
} |
||||
|
||||
private function single_curl() |
||||
{ |
||||
$ch = curl_init(); |
||||
// 从请求队列里面弹出一个来 |
||||
$request = array_shift($this->requests); |
||||
$options = $this->get_options($request); |
||||
curl_setopt_array($ch, $options); |
||||
$output = curl_exec($ch); |
||||
$info = curl_getinfo($ch); |
||||
$error = null; |
||||
if ($output === false) |
||||
{ |
||||
$error = curl_error( $ch ); |
||||
} |
||||
//$output = substr($output, 10); |
||||
//$output = gzinflate($output); |
||||
|
||||
// 其实一个请求的时候没是么必要回调,直接返回数据就好了,不过这里算是多一个功能吧,和多请求保持一样的操作 |
||||
if ($this->callback) |
||||
{ |
||||
if (is_callable($this->callback)) |
||||
{ |
||||
call_user_func($this->callback, $output, $info, $request, $error); |
||||
} |
||||
} |
||||
else |
||||
{ |
||||
return $output; |
||||
} |
||||
return true; |
||||
} |
||||
|
||||
private function rolling_curl($window_size = null) |
||||
{ |
||||
// 如何设置了最大任务数 |
||||
if ($window_size) |
||||
$this->window_size = $window_size; |
||||
|
||||
// 如果请求数 小于 任务数,设置任务数为请求数 |
||||
if (sizeof($this->requests) < $this->window_size) |
||||
$this->window_size = sizeof($this->requests); |
||||
|
||||
// 如果任务数小于2个,不应该用这个方法的,用上面的single_curl方法就好了 |
||||
if ($this->window_size < 2) |
||||
exit("Window size must be greater than 1"); |
||||
|
||||
// 初始化任务队列 |
||||
$master = curl_multi_init(); |
||||
|
||||
// 开始第一批请求 |
||||
for ($i = 0; $i < $this->window_size; $i++) |
||||
{ |
||||
$ch = curl_init(); |
||||
$options = $this->get_options($this->requests[$i]); |
||||
curl_setopt_array($ch, $options); |
||||
curl_multi_add_handle($master, $ch); |
||||
// 添加到请求数组 |
||||
$key = (string) $ch; |
||||
$this->requestMap[$key] = $i; |
||||
} |
||||
|
||||
do { |
||||
while (($execrun = curl_multi_exec($master, $running)) == CURLM_CALL_MULTI_PERFORM) ; |
||||
|
||||
// 如果 |
||||
if ($execrun != CURLM_OK) { break; } |
||||
|
||||
// 一旦有一个请求完成,找出来,因为curl底层是select,所以最大受限于1024 |
||||
while ($done = curl_multi_info_read($master)) |
||||
{ |
||||
// 从请求中获取信息、内容、错误 |
||||
$info = curl_getinfo($done['handle']); |
||||
$output = curl_multi_getcontent($done['handle']); |
||||
$error = curl_error($done['handle']); |
||||
|
||||
// 如果绑定了回调函数 |
||||
$callback = $this->callback; |
||||
if (is_callable($callback)) |
||||
{ |
||||
$key = (string) $done['handle']; |
||||
$request = $this->requests[$this->requestMap[$key]]; |
||||
unset($this->requestMap[$key]); |
||||
call_user_func($callback, $output, $info, $request, $error); |
||||
} |
||||
|
||||
// 一个请求完了,就加一个进来,一直保证5个任务同时进行 |
||||
if ($i < sizeof($this->requests) && isset($this->requests[$i]) && $i < count($this->requests)) |
||||
{ |
||||
$ch = curl_init(); |
||||
$options = $this->get_options($this->requests[$i]); |
||||
curl_setopt_array($ch, $options); |
||||
curl_multi_add_handle($master, $ch); |
||||
|
||||
// 添加到请求数组 |
||||
$key = (string) $ch; |
||||
$this->requestMap[$key] = $i; |
||||
$i++; |
||||
} |
||||
// 把请求已经完成了得 curl handle 删除 |
||||
curl_multi_remove_handle($master, $done['handle']); |
||||
} |
||||
|
||||
// 当没有数据的时候进行堵塞,把 CPU 使用权交出来,避免上面 do 死循环空跑数据导致 CPU 100% |
||||
if ($running) |
||||
{ |
||||
curl_multi_select($master, $this->timeout); |
||||
} |
||||
|
||||
} while ($running); |
||||
// 关闭任务 |
||||
curl_multi_close($master); |
||||
|
||||
// 把请求清空,否则没有重新 new rolling_curl(); 直接再次导入一批url的时候,就会把前面已经执行过的url又执行一轮 |
||||
unset($this->requests); |
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* @return void |
||||
*/ |
||||
public function __destruct() |
||||
{ |
||||
unset($this->window_size, $this->callback, $this->options, $this->headers, $this->requests); |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
<?php |
||||
|
||||
$arr = array('fff', 'ggg', '', ''); |
||||
$arr = array_filter($arr); |
||||
print_r($arr); |
||||
|
||||
|
@ -0,0 +1,32 @@ |
||||
<?php |
||||
|
||||
echo "Starting\n"; |
||||
|
||||
$gmworker = new GearmanWorker(); |
||||
$gmworker->addServer('10.10.10.238'); |
||||
$gmworker->addFunction("reverse", "reverse_fn"); |
||||
|
||||
print "Waiting for job...\n"; |
||||
while($gmworker->work()) |
||||
{ |
||||
if ($gmworker->returnCode() != GEARMAN_SUCCESS) |
||||
{ |
||||
echo "return_code: " . $gmworker->returnCode() . "\n"; |
||||
break; |
||||
} |
||||
//break; |
||||
} |
||||
|
||||
function reverse_fn($job) |
||||
{ |
||||
sleep(3); |
||||
echo $job->workload()."\n"; |
||||
return strrev($job->workload()); |
||||
} |
||||
|
||||
|
||||
echo "hello\n"; |
||||
?> |
||||
|
||||
|
||||
|
@ -0,0 +1,21 @@ |
||||
## 功能 |
||||
IYUU自动辅种工具,目前能对国内大部分的PT站点自动辅种;支持下载器集群,支持多盘位,支持多下载目录,支持远程连接等。 |
||||
|
||||
## 原理 |
||||
IYUU自动辅种工具(英文名:iyuuAutoReseed),是一款PHP语言编写的Private Tracker辅种脚本,通过计划任务或常驻内存,按指定频率调用transmission、qBittorrent下载软件的API接口,提取正在做种的info_hash提交到服务器API接口,根据API接口返回的数据拼接种子连接,提交给下载器,自动辅种各个站点。 |
||||
|
||||
## 运行环境 |
||||
所有具备PHP运行环境的所有平台! |
||||
例如:Linux、Windows、MacOS |
||||
|
||||
## 需求提交/错误反馈 |
||||
- 点击链接加入群聊【IYUU自动辅种交流】:[https://jq.qq.com/?_wv=1027&k=5JOfOlM][1] |
||||
|
||||
## 捐助开发者 |
||||
如果觉得我的付出,节约了您的宝贵时间,请随意打赏一杯咖啡!或者一杯水! |
||||
您所有的打赏将用于服务器续期,增加服务的延续性。 |
||||
![微信打赏.png][2] |
||||
|
||||
|
||||
[1]: https://jq.qq.com/?_wv=1027&k=5JOfOlM |
||||
[2]: https://www.iyuu.cn/usr/uploads/2019/12/801558607.png |
@ -0,0 +1,27 @@ |
||||
## 重点讲解Ourbits站点的鉴权配置 |
||||
博客链接:https://www.iyuu.cn/archives/337/ |
||||
IYUU自动辅种工具、Ourbits双方达成合作,可以对使用接口的用户,实现认证。 |
||||
### 申请爱语飞飞微信通知token,新用户访问:http://iyuu.cn 申请! |
||||
1.点击`开始使用`,出现二维码,用`微信扫码` |
||||
![微信通知1.png][1] |
||||
![微信通知2.png][2] |
||||
![微信通知3.png][3] |
||||
2.复制您的token令牌到`/app/config/config.php`文件内的`iyuu.cn`对应的配置字段,保存。如图: |
||||
![微信通知4.png][4] |
||||
|
||||
### 设置Ourbits: |
||||
![编辑配置4.png][5] |
||||
`passkey`,在你的控制面板 - 密钥 |
||||
`is_vip`,根据你的实际情况填写,因站点有下载种子的流控,如果你不在限制之列,可以`设置为1` |
||||
`id`,为用户中心打开后,浏览器地址栏**http://xxxxx.xxx/userdetails.php?id=`46880`**等号=后面的几个数字,如图: |
||||
![编辑配置6.png][6] |
||||
|
||||
到此,配置文件编辑完毕,请记得保存。 |
||||
如果提示保存格式,请保存为UTF8(无BOM)格式。 |
||||
|
||||
[1]: https://www.iyuu.cn/usr/uploads/2019/12/2331433923.png |
||||
[2]: https://www.iyuu.cn/usr/uploads/2019/12/3324442680.png |
||||
[3]: https://www.iyuu.cn/usr/uploads/2019/12/3181272964.png |
||||
[4]: https://www.iyuu.cn/usr/uploads/2019/12/3669828008.png |
||||
[5]: https://www.iyuu.cn/usr/uploads/2019/12/3696916642.png |
||||
[6]: https://www.iyuu.cn/usr/uploads/2019/12/1230288911.png |
@ -0,0 +1,10 @@ |
||||
## 【特别提示】 |
||||
php命令与脚本路径之间是有个空格,请注意!请注意!请注意! |
||||
|
||||
|
||||
## IYUU自动辅种命令: |
||||
`php ./iyuu.cn.php` |
||||
|
||||
|
||||
|
||||
### 【重要说明:实际路径,以你实际的为准,切勿生搬硬套!】 |
@ -0,0 +1,83 @@ |
||||
## 常见问题FAQ |
||||
|
||||
#### 问:这款脚本会不会泄露我的秘钥、cookie、客户端连接密码? |
||||
|
||||
答:绝对不会!!代码全开源,能经受审查!所有私密配置只在本地存储使用,绝不会发送给任何第三方。 |
||||
|
||||
#### 问:只使用IYUU自动辅种,需要配置各站的cookie吗? |
||||
|
||||
答:只需配置全局客户端和各网站的passkey密钥(没有配置passkey的站点,在辅种时候会跳过)。2019年12月28日补充:辅种hdcity、hdchina需要配置cookie。 |
||||
|
||||
#### 问:IYUU自动辅种工具,向服务器发送了什么实现自动辅种呢? |
||||
|
||||
答:1.文件`phpspider\app\torrent\cache\hashString.txt`是脚本发送给服务器的数据,是按下载器分组的种子info_hash;2.文件`phpspider\app\torrent\cache\reseed.txt`是服务器返回的可辅种数据。 |
||||
|
||||
#### 问:本次添加成功的辅种任务,下次辅种时还会重复添加吗? |
||||
|
||||
答:添加成功的辅种任务,会在本地生成缓存记录,避免重复添加辅种任务,路径在:`phpspider\app\torrent\cachehash`。 |
||||
|
||||
#### 问:为什么有些站点自动跳过? |
||||
|
||||
答:因为站点在下载种子时有流控或者人机验证,会导致辅种失败;但脚本会在`phpspider\app\torrent\cache`目录下生成以站点命名的手动辅种文本。 |
||||
|
||||
#### 问:我拥有辅种时自动跳过站点的特殊权限,如何设置为可以辅种呢? |
||||
|
||||
答:在站点的独立配置区域,添加一行代码`'is_vip' => 1,`即可。例如Ourbits: |
||||
|
||||
```php |
||||
// ourbits |
||||
'ourbits' => array( |
||||
// 如果需要用下载免费种脚本,须配置(只是自动辅种,可以不配置此项) |
||||
'cookie' => '', |
||||
// 如果需要自动辅种,必须配置 |
||||
'passkey' => '', |
||||
'id' => 46880, // 用户ID |
||||
'is_vip' => 1, // 是否具有VIP或特殊权限?0 普通,1 VIP |
||||
), |
||||
``` |
||||
|
||||
|
||||
|
||||
#### 问:如何升级到最新版本? |
||||
|
||||
答:从github或码云仓库,下载最新的源码,覆盖到本地即可。 |
||||
|
||||
#### 问:为啥我编辑配置后,运行的时候显示乱码? |
||||
|
||||
答:保存的编码格式不对,正确的格式为UTF8(无BOM);推荐编辑器:`VS code`、`EditPlu`s、`SublimeText`、`Notepad++`。 |
||||
|
||||
#### 问:为什么用IYUU自动辅种,有些种子无法校验通过? |
||||
|
||||
答:首先,这个属于正常现象。 只要IYUU自动辅种匹配过来,然后校验通不过的,分为以下几种情况: |
||||
|
||||
1、被改了文件名,重新做种 |
||||
|
||||
2、被改了顶层目录名,重新做种 |
||||
|
||||
3、把单文件放进了目录里面,重新做种 |
||||
|
||||
4、更改了部分文件,例如nfo文件,重新做种 |
||||
|
||||
一般情况下,通过分析种子结构,创建软连接,90%以上都可以辅种成功。 |
||||
也可以不管他或删除校验失败的任务(不要删除数据)。 |
||||
|
||||
#### 问:如何创建软连接、硬链接手动辅种? |
||||
|
||||
答:Windows命令: `mklink`, Linux命令: `ln -s`, 更详细的用法请百度。 |
||||
|
||||
#### 问:IYUU自动辅种,添加计划任务后多久运行一次比较合适? |
||||
|
||||
答:为减轻服务器压力,推荐间隔3小时以上(太频繁的调用接口,可能被封禁)。 |
||||
|
||||
#### 问:猫站的Tracker为啥是http,而不是https? |
||||
|
||||
答:请退出登录,在登录时勾选下面两个SSL的选项,登录后复制cookie,重新配置。 |
||||
|
||||
#### 问:如何反馈问题? |
||||
|
||||
答:1、点击链接加入群聊【IYUU自动辅种交流】:[https://jq.qq.com/?_wv=1027&k=5JOfOlM][1] |
||||
|
||||
2、QQ群:859882209 |
||||
|
||||
3、issues: https://gitee.com/ledc/IYUUAutoReseed/issues |
||||
|
@ -0,0 +1,15 @@ |
||||
## 开发计划 |
||||
| 功能 | 开发状态 | 预计开发时间 | 开发完成时间 | |
||||
| - | :-: | ---- | ---- | |
||||
| 微信鉴权 | 已完成 | 2019年12月22日 | 2019年12月23日 | |
||||
| 流控站点,手动辅种 | 已完成 | 2019年12月24日 | 2019年12月24日 | |
||||
| m-team IPv4、IPv6自定义配置 | 已完成 | 2019年12月25日 | 2019年12月25日 | |
||||
| 未配置客户端智能过滤 | 已完成 | 2019年12月25日 | 2019年12月25日 | |
||||
| 自动辅种结束微信通知 | 已完成 | 2019年12月25日 | 2019年12月27日 | |
||||
| 做种客户端间转移 | 已完成 | 2019年12月25日 | 2020年1月14日 | |
||||
| 手动辅种按目录分组 | 已完成 | 2019年12月26日 | 2020年1月14日 | |
||||
| WEB页面生成配置 | 暂未开始 | | | |
||||
| 自动转移客户端 | 暂未开始 | | | |
||||
| 脚本docker容器化 | 暂未开始 | | | |
||||
| 浏览器插件 | 暂未开始 | | | |
||||
| 合集自动拆包辅种 | 暂未开始 | | | |
@ -0,0 +1,109 @@ |
||||
### 2020年1月14日 |
||||
|
||||
更新hdbug域名,删除下载免费种冗余文件。 |
||||
|
||||
### 2020年1月10日 |
||||
|
||||
修复:qBittorrent打开自动管理时,自动辅种目录对应错误的问题。 |
||||
|
||||
### 2020年1月9日 |
||||
|
||||
优化:萌猫tracker的IP类型改为可配置; |
||||
优化:自动辅种时添加的任务,校验后自动暂停(无需更改全局)。 |
||||
|
||||
### 2020年1月5日 |
||||
|
||||
修复:城市cuhash变化无法辅种的问题 |
||||
|
||||
### 2020年1月1日 |
||||
|
||||
新增:scg |
||||
|
||||
修复: |
||||
|
||||
1.转移客户端做种支持磁力链 |
||||
|
||||
2.萌猫抓取问题 |
||||
|
||||
3.瓷器抓取标题的问题 |
||||
|
||||
### 2019年12月27日 |
||||
|
||||
新增功能:自动辅种结束,微信通知统计信息,优化城市适配,新增discfan(GZT)。 |
||||
|
||||
### 2019年12月25日 |
||||
|
||||
1.新增支持upxin(HDU)、oshen |
||||
|
||||
------ |
||||
|
||||
### 2019年12月25日 |
||||
1.馒头支持ipv4、ipv6选择 |
||||
2.未配置的全局客户端智能过滤,不会再影响自动辅种 |
||||
|
||||
------ |
||||
|
||||
### 2019年12月24日 |
||||
新增hdstreet、joyhd、u2 |
||||
|
||||
------ |
||||
|
||||
### 2019年12月23日 |
||||
鉴权模式上线试运行 |
||||
|
||||
------ |
||||
|
||||
### 2019年12月21日 |
||||
|
||||
新增兽站、opencd、hdbug; |
||||
|
||||
------ |
||||
|
||||
### 2019年12月20日 |
||||
|
||||
新增1ptba、hdtime |
||||
|
||||
------ |
||||
|
||||
### 2019年12月17日 |
||||
|
||||
新增站点瓷器; |
||||
|
||||
------ |
||||
|
||||
### 2019年12月16日 |
||||
|
||||
新增leaguehd、聆音; |
||||
|
||||
------ |
||||
|
||||
### 2019年12月15日 |
||||
|
||||
1.自动辅种20个站; |
||||
2.支持qBittorrent做种转transmission |
||||
3.新增qBittorrent自动辅种时的状态过滤,只辅种已完成的种子 |
||||
|
||||
------ |
||||
|
||||
### 2019年12月12日 |
||||
|
||||
目前支持17个站点的自动辅种; |
||||
目前支持18个站点下载免费种; |
||||
|
||||
------ |
||||
|
||||
### 2019年12月10日 |
||||
|
||||
自动辅种工具完成! |
||||
|
||||
------ |
||||
|
||||
### 2019年11月19日 |
||||
|
||||
我堡、天空 完美适配,支持大小、做种数、下载数筛选。 |
||||
|
||||
------ |
||||
|
||||
技术讨论及后续更新,请加入QQ群! |
||||
**群名称:IYUU自动辅种交流** |
||||
**QQ群号:859882209** |
@ -0,0 +1,102 @@ |
||||
以下教程以windows为基础进行讲解,其他系统同理。 |
||||
博客链接:https://www.iyuu.cn/archives/324/ |
||||
## 第一步 下载压缩包 |
||||
从[码云仓库][1],下载最新源码,解压缩到D盘的根目录下。 |
||||
|
||||
## 第二步 复制一份配置文件 |
||||
打开`D:\IYUUAutoReseed\app\config`目录,复制一份`config.sample.php`,另存为`config.php`。 |
||||
|
||||
这样操作后,需要升级新版本时,直接覆盖即可,不会影响到配置。 |
||||
|
||||
## 第三步 编辑配置文件 |
||||
提醒:千万不要用windows记事本来编辑配置文件(会导致乱码)!! |
||||
推荐编辑软件:`VS code`、`EditPlus`、`SublimeText`、`Notepad++`等(保存格式,选UTF8 无BOM); |
||||
配置文件内容较多,新手往往很迷茫,不知道改哪里,在这里我重点强调2个步骤: |
||||
`1.编辑全局客户端; 2.编辑各站的秘钥,即passkey。` |
||||
|
||||
其他配置,如果不懂也没有关系;先保持默认,等脚本运行起来,再修改也不迟。另外,修改时一定要细心,仔细看教程。 |
||||
打开`D:\IYUUAutoReseed\app\config\config.php`文件,如下图: |
||||
![编辑配置1.png][2] |
||||
|
||||
### 填写全局客户端 |
||||
上图红框内的是`transmission`的示例配置,绿框是`qBittorrent`的示例配置; |
||||
IYUU自动辅种工具,目前支持这两种下载器,支持多盘位,辅种时全自动对应资源的下载目录。 |
||||
1,编辑`transmission`下载器 |
||||
`http://127.0.0.1:9091/transmission/rpc`是下载器的连接参数,你要修改的部分是`127.0.0.1:9091`改成你的IP与端口(本机使用无需修改),局域网内的机器请填写局域网IP与端口;远程使用请填写DDNS的远程连接域名与端口。 |
||||
username是用户名、password是密码。 |
||||
如果你没有用到`transmission`下载器,请把红框的内容都删除。 |
||||
|
||||
2,编辑`qBittorrent`下载器 |
||||
方法与上一步相同,只需填写ip、端口、用户名、密码即可。如果您是windows下的qBittorrent,请参考下图打开`WEB用户界面`: |
||||
![qb设置WEB用户界面.png][3] |
||||
|
||||
因为我两个下载器都在用,编辑好后,如图: |
||||
![编辑配置2.png][4] |
||||
|
||||
### 填写各站秘钥passkey |
||||
IYUU自动辅种:需要您配置各站的passkey(没有配置passkey的站点会自动跳过)。 |
||||
从各站点的控制面板,找到您的`秘钥`复制粘贴过来即可。 |
||||
配置好后如图: |
||||
![编辑配置3.png][5] |
||||
|
||||
---------- |
||||
|
||||
|
||||
## 第四步,重点讲解Ourbits站点的配置 |
||||
IYUU自动辅种工具、Ourbits双方达成合作,可以对使用接口的用户,实现认证。 |
||||
### 申请爱语飞飞微信通知token,新用户访问:http://iyuu.cn 申请! |
||||
1.点击`开始使用`,出现二维码,用`微信扫码` |
||||
![微信通知1.png][6] |
||||
![微信通知2.png][7] |
||||
![微信通知3.png][8] |
||||
2.复制您的token令牌到`/app/config/config.php`文件内的`iyuu.cn`对应的配置字段,保存。如图: |
||||
![微信通知4.png][9] |
||||
|
||||
### 设置Ourbits: |
||||
![编辑配置4.png][10] |
||||
`passkey`,在你的控制面板 - 密钥 |
||||
`is_vip`,根据你的实际情况填写,因站点有下载种子的流控,如果你不在限制之列,可以`设置为1` |
||||
`id`,为用户中心打开后,浏览器地址栏**http://xxxxx.xxx/userdetails.php?id=`46880`**等号=后面的几个数字,如图: |
||||
![编辑配置6.png][11] |
||||
|
||||
到此,配置文件编辑完毕,请记得保存。 |
||||
如果提示保存格式,请保存为UTF8(无BOM)格式。 |
||||
|
||||
------ |
||||
|
||||
## 群晖、铁威马、威联通等Linux环境 |
||||
|
||||
经过上面步骤,其实已经完成了配置,只需要把脚本复制到设备内,用php命令运行脚本即可。 |
||||
|
||||
群晖php命令:`php` |
||||
|
||||
威联通php命令:`/mnt/ext/opt/apache/bin/php` |
||||
|
||||
铁威马php命令:`php` |
||||
|
||||
---------- |
||||
|
||||
## Windows安装PHP运行环境 |
||||
也可以去官方下载【https://www.php.net/downloads】,官方下载的记得开启`curl、fileinfo、mbstring`,这3个扩展。 |
||||
另外我打包了一份,下载地址: |
||||
微云链接:https://share.weiyun.com/5EiXLfn 密码:ezsvnb |
||||
下载回来是一个ZIP压缩包,解压到`D:\IYUUAutoReseed\`目录内,文件结构如图: |
||||
![编辑配置7.png][12] |
||||
点击红框内`执行辅种`即可。 |
||||
如果你前期严格按照配置一步步操作,这里会正常显示跑动的辅种列表。正常如图: |
||||
![编辑配置8.png][13] |
||||
|
||||
|
||||
[1]: https://gitee.com/ledc/IYUUAutoReseed |
||||
[2]: https://www.iyuu.cn/usr/uploads/2019/12/2720183833.png |
||||
[3]: https://www.iyuu.cn/usr/uploads/2019/12/405587689.png |
||||
[4]: https://www.iyuu.cn/usr/uploads/2019/12/441257656.png |
||||
[5]: https://www.iyuu.cn/usr/uploads/2019/12/890327305.png |
||||
[6]: https://www.iyuu.cn/usr/uploads/2019/12/2331433923.png |
||||
[7]: https://www.iyuu.cn/usr/uploads/2019/12/3324442680.png |
||||
[8]: https://www.iyuu.cn/usr/uploads/2019/12/3181272964.png |
||||
[9]: https://www.iyuu.cn/usr/uploads/2019/12/3669828008.png |
||||
[10]: https://www.iyuu.cn/usr/uploads/2019/12/3696916642.png |
||||
[11]: https://www.iyuu.cn/usr/uploads/2019/12/1230288911.png |
||||
[12]: https://www.iyuu.cn/usr/uploads/2019/12/3189986236.png |
||||
[13]: https://www.iyuu.cn/usr/uploads/2019/12/2523845772.png |
Loading…
Reference in new issue