IYUUAutoReseed初始化版本库v0.2.0

master v0.2.0
iyuu.cn 5 years ago
commit 822e4a2270
  1. 5
      .gitignore
  2. 58
      LICENSE
  3. 141
      app/Class/Bencode.php
  4. 313
      app/Class/Function.php
  5. 332
      app/Class/IFile.php
  6. 71
      app/Class/Oauth.php
  7. 368
      app/Class/Rpc.php
  8. 708
      app/Class/TransmissionRPC.php
  9. 288
      app/Class/qBittorrent.php
  10. 255
      app/Class/uTorrent.php
  11. 352
      app/config/config.sample.php
  12. 1
      app/config/version.php
  13. 42
      app/init.php
  14. 10
      composer.json
  15. 131
      composer.lock
  16. 612
      iyuu.cn.php
  17. 101
      readme.md
  18. 7
      vendor/autoload.php
  19. 445
      vendor/composer/ClassLoader.php
  20. 21
      vendor/composer/LICENSE
  21. 17
      vendor/composer/autoload_classmap.php
  22. 10
      vendor/composer/autoload_files.php
  23. 10
      vendor/composer/autoload_namespaces.php
  24. 10
      vendor/composer/autoload_psr4.php
  25. 70
      vendor/composer/autoload_real.php
  26. 58
      vendor/composer/autoload_static.php
  27. 119
      vendor/composer/installed.json
  28. 11
      vendor/curl/curl/.gitignore
  29. 113
      vendor/curl/curl/.gitlab-ci.yml
  30. 20
      vendor/curl/curl/LICENSE
  31. 125
      vendor/curl/curl/README.md
  32. 36
      vendor/curl/curl/composer.json
  33. 24
      vendor/curl/curl/phpunit.xml.dist
  34. 719
      vendor/curl/curl/src/Curl/Curl.php
  35. 277
      vendor/curl/curl/tests/CurlTest.php
  36. 13
      vendor/curl/curl/tests/data/response_headers_with_continue.txt
  37. BIN
      vendor/curl/curl/tests/data/test.png
  38. 9
      vendor/curl/curl/tests/server/Dockerfile
  39. 37
      vendor/curl/curl/tests/server/php-curl-test/deploy.php
  40. 14
      vendor/curl/curl/tests/server/php-curl-test/http_basic_auth.php
  41. 21
      vendor/curl/curl/tests/server/php-curl-test/post_file_path_upload.php
  42. 4
      vendor/curl/curl/tests/server/php-curl-test/post_multidimensional.php
  43. 31
      vendor/curl/curl/tests/server/php-curl-test/server.php
  44. 52
      vendor/owner888/phpspider/README.md
  45. 77
      vendor/owner888/phpspider/autoloader.php
  46. 38
      vendor/owner888/phpspider/composer.json
  47. 64
      vendor/owner888/phpspider/core/cache.php
  48. 55
      vendor/owner888/phpspider/core/constants.php
  49. 579
      vendor/owner888/phpspider/core/db.php
  50. 101
      vendor/owner888/phpspider/core/init.php
  51. 119
      vendor/owner888/phpspider/core/log.php
  52. 2870
      vendor/owner888/phpspider/core/phpspider.bak20170807.php
  53. 3598
      vendor/owner888/phpspider/core/phpspider.php
  54. 1388
      vendor/owner888/phpspider/core/queue.php
  55. 998
      vendor/owner888/phpspider/core/requests.php
  56. 588
      vendor/owner888/phpspider/core/selector.php
  57. 936
      vendor/owner888/phpspider/core/util.php
  58. 421
      vendor/owner888/phpspider/core/worker.php
  59. 20
      vendor/owner888/phpspider/gitadd.sh
  60. 129
      vendor/owner888/phpspider/hacked-emails/banners.txt
  61. 49
      vendor/owner888/phpspider/hacked-emails/hacked_emails.php
  62. 425
      vendor/owner888/phpspider/library/cls_curl.php
  63. 248
      vendor/owner888/phpspider/library/cls_query.php
  64. 1263
      vendor/owner888/phpspider/library/cls_redis.php
  65. 121
      vendor/owner888/phpspider/library/cls_redis_client.php
  66. 179
      vendor/owner888/phpspider/library/cls_redis_server.php
  67. 5727
      vendor/owner888/phpspider/library/phpquery.php
  68. 466
      vendor/owner888/phpspider/library/rolling_curl.php
  69. 7
      vendor/owner888/phpspider/test.php
  70. 32
      vendor/owner888/phpspider/worker.php
  71. 21
      wiki/Home.md
  72. 27
      wiki/合作站点鉴权配置.md
  73. 10
      wiki/命令汇总.md
  74. 83
      wiki/常见问题.md
  75. 15
      wiki/开发计划.md
  76. 109
      wiki/更新历史.md
  77. 102
      wiki/自动辅种最简配置(windows篇).md
  78. BIN
      微信赞赏码.png

5
.gitignore vendored

@ -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"]
}
}

131
composer.lock generated

@ -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

Binary file not shown.

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;">&hearts; &hearts;</span> |] | Git Deployment Script v0.1 |
|___==___| / &copy; 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://www.epooll.com/zhihu/pachong.gif)
更多详细内容,移步到:
[开发文档](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

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Loading…
Cancel
Save