移除、添加文件

This commit is contained in:
david
2021-01-13 13:53:54 +08:00
parent 87d9f81bad
commit e61b47f3e8
42 changed files with 1902 additions and 3516 deletions

View File

@ -0,0 +1,6 @@
/.git
composer.lock
vendor
vendor/
.idea
.idea/

View File

@ -0,0 +1,26 @@
{
"name": "ledccn/bittorrentclient",
"description": "实现对下载服务器管理的代码库",
"type": "library",
"keywords": ["transmission", "qBittorrent"],
"homepage": "https://github.com/ledccn/BittorrentClient",
"license": "GPL-3.0-or-later",
"authors": [
{
"name": "David",
"email": "367013672@qq.com"
}
],
"require": {
"php": "^5.6 | ^7.0",
"curl/curl": "^2.3",
"ext-curl": "*",
"ext-json": "*",
"ext-mbstring": "*"
},
"autoload": {
"psr-4": {
"IYUU\\Client\\": "src"
}
}
}

View File

@ -0,0 +1,23 @@
## 项目简介
下载服务器的支持库,目前支持
- transmission
- qBittorrent
## 使用方法
https://packagist.org/packages/ledccn/bittorrentclient
```php
composer require ledccn/bittorrentclient:dev-master
```
## 其他项目
| 名称 | 简介 |
| ---------------------------------------------------------- | ------------------------------------------------------------ |
| [IYUUAutoReseed](https://github.com/ledccn/IYUUAutoReseed) | IYUU自动辅种工具功能分为两大块自动辅种、自动转移。目前能对支持的PT站点自动辅种支持下载器集群支持多盘位支持多下载目录支持远程连接等可以实现各下载器之间自动转移做种客户端让下载器各司其职专职的保种、专职的下载。 |

View File

@ -0,0 +1,145 @@
<?php
/**
* 下载服务器抽象类
* Created by PhpStorm
* User: David <367013672@qq.com>
* Date: 2020-1-11
*/
namespace IYUU\Client;
abstract class AbstractClient
{
/**
* 完整的下载服务器地址
* @var string
*/
protected $url = '';
/**
* 下载服务器用户名
* @var string
*/
protected $username = '';
/**
* 密码
* @var string
*/
protected $password = '';
/**
* 调试开关
* @var bool
*/
public $debug = false;
/**
* 公共方法:创建客户端实例
* @param array $config
* array(
* 'type' => '',
* 'host' => '',
* 'endpoint' => '',
* 'username' => '',
* 'password' => '',
* )
* @return mixed 客户端实例
* @throws \IYUU\Client\ClientException
*/
public static function create($config = [])
{
// 下载服务器类型
$type = isset($config['type']) ? $config['type'] : '';
$file = __DIR__ . DIRECTORY_SEPARATOR . $type . DIRECTORY_SEPARATOR . $type .'.php';
if (!is_file($file)) {
throw new ClientException($file.' 文件不存在');
}
$className = "IYUU\\Client\\" . $type . "\\" . $type;
if (class_exists($className)) {
echo $type." 客户端正在实例化!".PHP_EOL;
return new $className($config);
} else {
throw new ClientException($className.' 客户端class不存在');
}
}
/**
* 初始化必须的参数
* @descr 子类调用
* @param array $config
*/
protected function initialize($config = [])
{
$host = isset($config['host']) ? $config['host'] : ''; // 地址端口
$endpoint = isset($config['endpoint']) ? $config['endpoint'] : ''; // 接入点
$username = isset($config['username']) ? $config['username'] : ''; // 用户名
$password = isset($config['password']) ? $config['password'] : ''; // 密码
$debug = isset($config['debug']) ? $this->booleanParse($config['debug']) : false; // 调试开关
$this->url = rtrim($host, '/') . $endpoint;
$this->username = $username;
$this->password = $password;
$this->debug = $debug;
}
/**
* 对布尔型进行格式化
* @param mixed $value 变量值
* @return boolean/string 格式化后的变量
*/
public function booleanParse($value)
{
$rs = $value;
if (!is_bool($value)) {
if (is_numeric($value)) {
$rs = $value > 0 ? true : false;
} elseif (is_string($value)) {
$rs = in_array(strtolower($value), ['ok', 'true', 'success', 'on', 'yes', '(ok)', '(true)', '(success)', '(on)', '(yes)']) ? true : false;
} else {
$rs = $value ? true : false;
}
}
return $rs;
}
/**
* 查询Bittorrent客户端状态
* @return string
*/
abstract public function status();
/**
* 获取所有种子的列表
* @param array $move
* @return array(
* 'hash' => string json,
* 'sha1' => string,
* 'hashString '=> array
* )
*/
abstract public function all(&$move = array());
/**
* 添加种子连接
* @param string $torrent_url
* @param string $save_path
* @param array $extra_options
*/
abstract public function add($torrent_url, $save_path = '', $extra_options = array());
/**
* 添加种子原数据
* @param string $torrent_metainfo
* @param string $save_path
* @param array $extra_options
*/
abstract public function add_metainfo($torrent_metainfo, $save_path = '', $extra_options = array());
/**
* 删除种子
* @param $torrent
* @param bool $deleteFiles
*/
abstract public function delete($torrent, $deleteFiles = false);
}

View File

@ -0,0 +1,12 @@
<?php
namespace IYUU\Client;
use Exception;
/**
* Class ClientException
*/
class ClientException extends Exception
{
}

View File

@ -0,0 +1,11 @@
{
"name": "qBittorrent",
"author": "David",
"homepage": "https://github.com/qbittorrent/qBittorrent",
"version": "1.0",
"icon": "https://www.iyuu.cn/usr/uploads/client/qBittorrent.ico",
"allowCustomPath": true,
"pathDescription": "当前目录列表配置是指定硬盘上的绝对路径,如 /volume1/music/ 或 D:\\download\\music\\",
"description": "当前支持 qBittorrent v4.1+,由于浏览器限制,需要禁用 qBittorrent 的『启用跨站请求伪造(CSRF)保护』功能才能正常使用",
"warning": "注意:由于 qBittorrent 验证机制限制,第一次测试连接成功后,后续测试无论密码正确与否都会提示成功。"
}

View File

@ -0,0 +1,674 @@
<?php
namespace IYUU\Client\qBittorrent;
use Curl\Curl;
use IYUU\Client\AbstractClient;
use IYUU\Client\ClientException;
/**
* qBittorrent下载服务器的API操作类
* 开源项目地址https://github.com/qbittorrent/qBittorrent
* API文档https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation
*/
class qBittorrent extends AbstractClient
{
/**
* API主版本号
* @var int|mixed|string
*/
private $api_version = '';
/**
* CSRF使用的Session或者Cookie
* @var string
*/
private $session_id = '';
/**
* curl实例
* @var Curl
*/
private $curl;
/**
* 分隔符
* @var string
*/
protected $delimiter = '';
/**
* 各版的API接入点
* @var array
*/
private $endpoints = [
'login' => [
'1' => '/login',
'2' => '/api/v2/auth/login'
],
'logout'=> [
'1' => null,
'2' => '/api/v2/auth/logout'
],
'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'
],
'downloadLimit' => [
'1' => null,
'2' => '/api/v2/transfer/downloadLimit'
],
'setDownloadLimit' => [
'1' => null,
'2' => '/api/v2/transfer/setDownloadLimit'
],
'uploadLimit' => [
'1' => null,
'2' => '/api/v2/transfer/uploadLimit'
],
'setUploadLimit' => [
'1' => null,
'2' => '/api/v2/transfer/setUploadLimit'
],
'torrent_list' => [
'1' => null,
'2' => '/api/v2/torrents/info'
],
'torrent_properties' => [
'1' => null,
'2' => '/api/v2/torrents/properties'
],
'torrent_trackers' => [
'1' => null,
'2' => '/api/v2/torrents/trackers'
],
'torrent_files' => [
'1' => null,
'2' => '/api/v2/torrents/files'
],
'torrent_pieceStates' => [
'1' => null,
'2' => '/api/v2/torrents/pieceStates'
],
'torrent_pieceHashes' => [
'1' => null,
'2' => '/api/v2/torrents/pieceHashes'
],
'torrent_pause' => [
'1' => null,
'2' => '/api/v2/torrents/pause'
],
'torrent_resume' => [
'1' => null,
'2' => '/api/v2/torrents/resume'
],
'torrent_delete' => [
'1' => null,
'2' => '/api/v2/torrents/delete'
],
'torrent_recheck' => [
'1' => null,
'2' => '/api/v2/torrents/recheck' // 重新校验种子
],
'torrent_reannounce' => [
'1' => null,
'2' => '/api/v2/torrents/reannounce' // 重新宣告种子
],
'torrent_add' => [
'1' => null,
'2' => '/api/v2/torrents/add'
],
'torrent_addTrackers' => [
'1' => null,
'2' => '/api/v2/torrents/addTrackers'
],
'torrent_editTracker' => [
'1' => null,
'2' => '/api/v2/torrents/editTracker'
],
'torrent_removeTrackers' => [
'1' => null,
'2' => '/api/v2/torrents/removeTrackers'
],
'torrent_addPeers' => [
'1' => null,
'2' => '/api/v2/torrents/addPeers'
],
'torrent_increasePrio' => [
'1' => null,
'2' => '/api/v2/torrents/increasePrio'
],
'torrent_decreasePrio' => [
'1' => null,
'2' => '/api/v2/torrents/decreasePrio'
],
'torrent_downloadLimit' => [
'1' => null,
'2' => '/api/v2/torrents/downloadLimit'
],
'torrent_setDownloadLimit' => [
'1' => null,
'2' => '/api/v2/torrents/setDownloadLimit'
],
'torrent_setShareLimits' => [
'1' => null,
'2' => '/api/v2/torrents/setShareLimits'
],
'torrent_uploadLimit' => [
'1' => null,
'2' => '/api/v2/torrents/uploadLimit'
],
'torrent_setUploadLimit' => [
'1' => null,
'2' => '/api/v2/torrents/setUploadLimit'
],
'torrent_setLocation' => [
'1' => null,
'2' => '/api/v2/torrents/setLocation'
],
'torrent_rename' => [
'1' => null,
'2' => '/api/v2/torrents/rename'
],
'torrent_setCategory' => [
'1' => null,
'2' => '/api/v2/torrents/setCategory'
],
'torrent_categories' => [
'1' => null,
'2' => '/api/v2/torrents/categories'
],
'torrent_createCategory' => [
'1' => null,
'2' => '/api/v2/torrents/createCategory'
],
'torrent_editCategory' => [
'1' => null,
'2' => '/api/v2/torrents/editCategory'
],
'torrent_removeCategories' => [
'1' => null,
'2' => '/api/v2/torrents/removeCategories'
],
'torrent_addTags' => [
'1' => null,
'2' => '/api/v2/torrents/addTags'
],
'torrent_removeTags' => [
'1' => null,
'2' => '/api/v2/torrents/removeTags'
],
'torrent_tags' => [
'1' => null,
'2' => '/api/v2/torrents/tags'
],
'torrent_createTags' => [
'1' => null,
'2' => '/api/v2/torrents/createTags'
],
'torrent_deleteTags' => [
'1' => null,
'2' => '/api/v2/torrents/deleteTags'
],
'torrent_setAutoManagement' => [
'1' => null,
'2' => '/api/v2/torrents/setAutoManagement'
],
'torrent_toggleSequentialDownload' => [
'1' => null,
'2' => '/api/v2/torrents/toggleSequentialDownload'
],
'torrent_toggleFirstLastPiecePrio' => [
'1' => null,
'2' => '/api/v2/torrents/toggleFirstLastPiecePrio'
],
'torrent_setForceStart' => [
'1' => null,
'2' => '/api/v2/torrents/setForceStart'
],
'torrent_setSuperSeeding' => [
'1' => null,
'2' => '/api/v2/torrents/setSuperSeeding'
],
'torrent_renameFile' => [
'1' => null,
'2' => '/api/v2/torrents/renameFile'
],
'maindata' => [
'1' => null,
'2' => '/api/v2/sync/maindata'
],
];
/**
* 构造函数
* @param array $config
* @throws ClientException
*/
public function __construct($config = [])
{
$this->initialize($config);
$this->api_version = isset($config['api_version']) && $config['api_version'] ? $config['api_version'] : 2;
$this->curl = new Curl();
$this->curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); // 禁止验证证书
$this->curl->setOpt(CURLOPT_SSL_VERIFYHOST, 2); // 不检查证书
$this->curl->setOpt(CURLOPT_CONNECTTIMEOUT, 60); // 超时
$this->curl->setOpt(CURLOPT_TIMEOUT, 600); // 超时
if (!$this->login()) {
throw new ClientException("qBittorrent Unable to authenticate with Web Api.");
}
}
/**
* app编译版本
* @return string
*/
public function appVersion()
{
return $this->getData('app_version');
}
/**
* api版本
* @return string
*/
public function apiVersion()
{
return $this->getData('api_version');
}
/**
* 编译信息
* @return array
*/
public function buildInfo()
{
return $this->getData('build_info');
}
/**
* 下载器验证
* @return bool
*/
public function login()
{
$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->session_id = $matches[0];
$qb415 = '; QB_'.$matches[0]; // 兼容qBittorrent v4.1.5[小钢炮等]
$this->curl->setHeader('Cookie', $matches[0].$qb415);
return true;
}
};
return false;
}
/**
* 退出登录
* @return mixed
*/
public function logout()
{
$this->session_id = '';
$this->getData('logout');
$this->curl->reset();
return $this;
}
/**
* 获取下载器首选项
* @return mixed
*/
public function preferences()
{
return $this->getData('preferences');
}
/**
* 设置下载器首选项
* @param array $data
* @return array|mixed
*/
public function setPreferences($data = [])
{
if (!empty($data)) {
return $this->postData('setPreferences', ['json' => json_encode($data)]);
}
return [];
}
/**
* 获取种子列表
* @return mixed
*/
public function torrentList()
{
return $this->getData('torrent_list');
}
/**
* 添加种子链接
* @param $torrent_url
* @param string $save_path
* @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);
}
/**
* 添加种子元数据
* @param string $torrent_metainfo
* @param string $save_path
* @param array $extra_options
* @return false|string|null
*/
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);
// 设置请求头
$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);
}
/**
* 删除所有种子
* @param bool $deleteFiles
* @return string
*/
public function deleteAll($deleteFiles = false)
{
$torrents = json_decode($this->torrentList());
$response = '';
foreach ($torrents as $torrent) {
$response .= $this->delete($torrent->hash, $deleteFiles);
}
return $response;
}
/**
* 暂停种子
* @param string $hash info_hash可以|分隔删除多个种子也可以传入all删除所有种子
* @return false|string|null
*/
public function pause($hash)
{
return $this->postData('torrent_pause', ['hashes' => $hash]);
}
/**
* 恢复做种
* @param string $hash info_hash可以|分隔删除多个种子也可以传入all删除所有种子
* @return false|string|null
*/
public function resume($hash)
{
return $this->postData('torrent_resume', ['hashes' => $hash]);
}
/**
* 抽象方法,子类实现
* 删除种子
* @param string $hash info_hash可以|分隔删除多个种子也可以传入all删除所有种子
* @param bool $deleteFiles 是否同时删除数据
* @return false|string|null
*/
public function delete($hash = '', $deleteFiles = false)
{
return $this->postData('torrent_delete', ['hashes' => $hash, 'deleteFiles' => $deleteFiles ? 'true':'false']);
}
/**
* 重新校验种子
* @param string $hash info_hash可以|分隔删除多个种子也可以传入all删除所有种子
* @return false|string|null
*/
public function recheck($hash)
{
return $this->postData('torrent_recheck', ['hashes' => $hash]);
}
/**
* 重新宣告种子
* @param string $hash info_hash可以|分隔删除多个种子也可以传入all删除所有种子
* @return false|string|null
*/
public function reannounce($hash)
{
return $this->postData('torrent_reannounce', ['hashes' => $hash]);
}
/**
* @param $hash
* @param $location
* @return false|string|null
*/
public function setTorrentLocation($hash, $location)
{
return $this->postData('torrent_setLocation', ['hashes' => $hash, 'location' => $location]);
}
/**
* 获取当前Curl对象
* @return Curl
*/
public function curl()
{
return $this->curl;
}
/**
* 基本get方法
* @param $endpoint
* @return mixed
*/
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);
var_dump($this->curl->response);
}
if ($this->curl->error) {
return $this->errorMessage();
}
return $this->curl->response;
}
/**
* 基本post方法
* @param $endpoint
* @param $data
* @return mixed
*/
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);
var_dump($this->curl->response);
}
if ($this->curl->error) {
return $this->errorMessage();
}
return $this->curl->response;
}
/**
* 返回错误信息
* @return string
*/
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
* @param array $param
* @return string
*/
public 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. '"' . $eol . $eol;
$data .= $content . $eol;
}
$data .= "--" . $this->delimiter . "--" . $eol;
return $data;
}
/**
* 拼接种子上传文件流 multipart/form-data
* https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation#add-new-torrent
* @param array $param
* @return string
*/
public function buildData($param)
{
$this->delimiter = uniqid();
$eol = "\r\n";
$data = '';
// 拼接文件流
$data .= "--" . $this->delimiter . $eol;
$data .= 'Content-Disposition: form-data; name="' .$param['name']. '"; filename="'.$param['filename'].'"' . $eol;
$data .= 'Content-Type: application/x-bittorrent' . $eol . $eol;
$data .= $param['torrents'] . $eol;
unset($param['name']);
unset($param['filename']);
unset($param['torrents']);
if (!empty($param)) {
foreach ($param as $name => $content) {
$data .= "--" . $this->delimiter . $eol;
$data .= 'Content-Disposition: form-data; name="' . $name . '"' . $eol . $eol;
$data .= $content . $eol;
}
}
$data .= "--" . $this->delimiter . "--" . $eol;
return $data;
}
/**
* 抽象方法,子类实现
*/
public function status()
{
return $this->appVersion();
}
/**
* 抽象方法,子类实现
* @param array $torrentList
* @return array
*/
public function all(&$torrentList = array())
{
$result = $this->getData('torrent_list');
$res = json_decode($result, true);
if (empty($res)) {
echo "获取种子列表失败可能qBittorrent暂时无响应请稍后重试".PHP_EOL;
return array();
}
// 过滤,只保留正常做种
$res = array_filter($res, function ($v) {
if (isset($v['state']) && in_array($v['state'], array('uploading','stalledUP','pausedUP','queuedUP','checkingUP','forcedUP'))) {
return true;
}
return false;
}, ARRAY_FILTER_USE_BOTH);
if (empty($res)) {
echo "未获取到正常做种数据,请多保种,然后重试!".PHP_EOL;
return array();
}
// 提取数组hashString
$info_hash = array_column($res, 'hash');
// 升序排序
sort($info_hash);
$json = json_encode($info_hash, JSON_UNESCAPED_UNICODE);
// 去重 应该从文件读入,防止重复提交
$sha1 = sha1($json);
// 组装返回数据
$hashArray['hash'] = $json;
$hashArray['sha1'] = $sha1;
// 变换数组hashString键名、目录为键值
$hashArray['hashString'] = array_column($res, "save_path", 'hash');
$torrentList = array_column($res, null, 'hash');
return $hashArray;
}
}

View File

@ -0,0 +1,10 @@
{
"name": "Transmission",
"author": "David",
"homepage": "https://github.com/transmission/transmission",
"version": "1.0",
"icon": "https://www.iyuu.cn/usr/uploads/client/transmission.ico",
"allowCustomPath": true,
"pathDescription": "当前目录列表配置是指定硬盘上的绝对路径,如 /volume1/music/",
"description": "默认情况下,系统会请求 http://ip:port/transmission/rpc 这个路径,如果无法连接,请确认 `settings.json` 文件的 `rpc-url` 值;"
}

View File

@ -0,0 +1,990 @@
<?php
namespace IYUU\Client\transmission;
use Curl\Curl;
use IYUU\Client\AbstractClient;
use IYUU\Client\ClientException;
/**
* Transmission下载服务器的RPC操作类
* 开源项目地址https://github.com/transmission/transmission
* API文档https://github.com/transmission/transmission/blob/master/extras/rpc-spec.txt
*/
class transmission extends AbstractClient
{
/**
* UserAgent
*/
const UA = 'TransmissionRPC for PHP/7.0.0';
/**
* Transmission RPC version
* @var int
*/
protected $rpc_version = 0;
/**
* CSRF使用的Session或者Cookie
* @var string
*/
protected $session_id = '';
/**
* 种子状态码 torrent status
*/
const TR_STATUS_STOPPED = 0; // Torrent is stopped
const TR_STATUS_CHECK_WAIT = 1; // Queued to check files
const TR_STATUS_CHECK = 2; // Checking files
const TR_STATUS_DOWNLOAD_WAIT = 3; // Queued to download
const TR_STATUS_DOWNLOAD = 4; // Downloading
const TR_STATUS_SEED_WAIT = 5; // Queued to seed
const TR_STATUS_SEED = 6; // Seeding
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;
/**
* 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;
/**
* Curl实例
* @var
*/
private $curl;
/**
* 构造函数
* @param array $config
*/
public function __construct($config = [])
{
$this->initialize($config);
$this->curl = new Curl();
$this->curl->setOpt(CURLOPT_CONNECTTIMEOUT, 60); // 超时
$this->curl->setOpt(CURLOPT_TIMEOUT, 600); // 超时
}
/**
* 开始一个或多个种子
* Start one or more torrents
* @param int|array ids A list of transmission torrent ids
* @return mixed
* @throws ClientException
*/
public function start($ids)
{
if (!is_array($ids)) {
$ids = array($ids);
}
$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
* @return mixed
* @throws ClientException
*/
public function stop($ids)
{
if (!is_array($ids)) {
$ids = array($ids);
}
$request = array("ids" => $ids);
return $this->request("torrent-stop", $request);
}
/**
* 校验一个或多个种子
* Verify one or more torrents
*
* @param int|array ids A list of transmission torrent ids
* @return mixed
* @throws ClientException
*/
public function verify($ids)
{
if (!is_array($ids)) {
$ids = array($ids);
}
$request = array("ids" => $ids);
return $this->request("torrent-verify", $request);
}
/**
* 删除一个或多个种子
* Remove torrent from transmission
* @param int|array ids A list of transmission torrent ids
* @param bool $delete_local_data 是否删除数据
* @return mixed
* @throws ClientException
*/
public function delete($ids, $delete_local_data = false)
{
if (!is_array($ids)) {
$ids = array($ids);
}
$request = array(
"ids" => $ids,
"delete-local-data" => $delete_local_data
);
return $this->request("torrent-remove", $request);
}
/**
* 从announce获取一个或多个种子的更多Peer
* Reannounce one or more torrents
* @param int|array ids A list of transmission torrent ids
* @return mixed
* @throws ClientException
*/
public function reannounce($ids)
{
if (!is_array($ids)) {
$ids = array($ids);
}
$request = array("ids" => $ids);
return $this->request("torrent-reannounce", $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".
* key | type | source
----------------------------+-----------------------------+---------
activityDate | number | tr_stat
addedDate | number | tr_stat
bandwidthPriority | number | tr_priority_t
comment | string | tr_info
corruptEver | number | tr_stat
creator | string | tr_info
dateCreated | number | tr_info
desiredAvailable | number | tr_stat
doneDate | number | tr_stat
downloadDir | string | tr_torrent
downloadedEver | number | tr_stat
downloadLimit | number | tr_torrent
downloadLimited | boolean | tr_torrent
error | number | tr_stat
errorString | string | tr_stat
eta | number | tr_stat
etaIdle | number | tr_stat
files | array (see below) | n/a
fileStats | array (see below) | n/a
hashString | string | tr_info
haveUnchecked | number | tr_stat
haveValid | number | tr_stat
honorsSessionLimits | boolean | tr_torrent
id | number | tr_torrent
isFinished | boolean | tr_stat
isPrivate | boolean | tr_torrent
isStalled | boolean | tr_stat
leftUntilDone | number | tr_stat
magnetLink | string | n/a
manualAnnounceTime | number | tr_stat
maxConnectedPeers | number | tr_torrent
metadataPercentComplete | double | tr_stat
name | string | tr_info
peer-limit | number | tr_torrent
peers | array (see below) | n/a
peersConnected | number | tr_stat
peersFrom | object (see below) | n/a
peersGettingFromUs | number | tr_stat
peersSendingToUs | number | tr_stat
percentDone | double | tr_stat
pieces | string (see below) | tr_torrent
pieceCount | number | tr_info
pieceSize | number | tr_info
priorities | array (see below) | n/a
queuePosition | number | tr_stat
rateDownload (B/s) | number | tr_stat
rateUpload (B/s) | number | tr_stat
recheckProgress | double | tr_stat
secondsDownloading | number | tr_stat
secondsSeeding | number | tr_stat
seedIdleLimit | number | tr_torrent
seedIdleMode | number | tr_inactvelimit
seedRatioLimit | double | tr_torrent
seedRatioMode | number | tr_ratiolimit
sizeWhenDone | number | tr_stat
startDate | number | tr_stat
status | number | tr_stat
trackers | array (see below) | n/a
trackerStats | array (see below) | n/a
totalSize | number | tr_info
torrentFile | string | tr_info
uploadedEver | number | tr_stat
uploadLimit | number | tr_torrent
uploadLimited | boolean | tr_torrent
uploadRatio | double | tr_stat
wanted | array (see below) | n/a
webseeds | array (see below) | n/a
webseedsSendingToUs | number | tr_stat
| |
-------------------+--------+-----------------------------+
files | array of objects, each containing: |
+-------------------------+------------+
| bytesCompleted | number | tr_torrent
| length | number | tr_info
| name | string | tr_info
-------------------+--------------------------------------+
fileStats | a file's non-constant properties. |
| array of tr_info.filecount objects, |
| each containing: |
+-------------------------+------------+
| bytesCompleted | number | tr_torrent
| wanted | boolean | tr_info
| priority | number | tr_info
-------------------+--------------------------------------+
peers | array of objects, each containing: |
+-------------------------+------------+
| address | string | tr_peer_stat
| clientName | string | tr_peer_stat
| clientIsChoked | boolean | tr_peer_stat
| clientIsInterested | boolean | tr_peer_stat
| flagStr | string | tr_peer_stat
| isDownloadingFrom | boolean | tr_peer_stat
| isEncrypted | boolean | tr_peer_stat
| isIncoming | boolean | tr_peer_stat
| isUploadingTo | boolean | tr_peer_stat
| isUTP | boolean | tr_peer_stat
| peerIsChoked | boolean | tr_peer_stat
| peerIsInterested | boolean | tr_peer_stat
| port | number | tr_peer_stat
| progress | double | tr_peer_stat
| rateToClient (B/s) | number | tr_peer_stat
| rateToPeer (B/s) | number | tr_peer_stat
-------------------+--------------------------------------+
peersFrom | an object containing: |
+-------------------------+------------+
| fromCache | number | tr_stat
| fromDht | number | tr_stat
| fromIncoming | number | tr_stat
| fromLpd | number | tr_stat
| fromLtep | number | tr_stat
| fromPex | number | tr_stat
| fromTracker | number | tr_stat
-------------------+--------------------------------------+
pieces | A bitfield holding pieceCount flags | tr_torrent
| which are set to 'true' if we have |
| the piece matching that position. |
| JSON doesn't allow raw binary data, |
| so this is a base64-encoded string. |
-------------------+--------------------------------------+
priorities | an array of tr_info.filecount | tr_info
| numbers. each is the tr_priority_t |
| mode for the corresponding file. |
-------------------+--------------------------------------+
trackers | array of objects, each containing: |
+-------------------------+------------+
| announce | string | tr_tracker_info
| id | number | tr_tracker_info
| scrape | string | tr_tracker_info
| tier | number | tr_tracker_info
-------------------+--------------------------------------+
trackerStats | array of objects, each containing: |
+-------------------------+------------+
| announce | string | tr_tracker_stat
| announceState | number | tr_tracker_stat
| downloadCount | number | tr_tracker_stat
| hasAnnounced | boolean | tr_tracker_stat
| hasScraped | boolean | tr_tracker_stat
| host | string | tr_tracker_stat
| id | number | tr_tracker_stat
| isBackup | boolean | tr_tracker_stat
| lastAnnouncePeerCount | number | tr_tracker_stat
| lastAnnounceResult | string | tr_tracker_stat
| lastAnnounceStartTime | number | tr_tracker_stat
| lastAnnounceSucceeded | boolean | tr_tracker_stat
| lastAnnounceTime | number | tr_tracker_stat
| lastAnnounceTimedOut | boolean | tr_tracker_stat
| lastScrapeResult | string | tr_tracker_stat
| lastScrapeStartTime | number | tr_tracker_stat
| lastScrapeSucceeded | boolean | tr_tracker_stat
| lastScrapeTime | number | tr_tracker_stat
| lastScrapeTimedOut | boolean | tr_tracker_stat
| leecherCount | number | tr_tracker_stat
| nextAnnounceTime | number | tr_tracker_stat
| nextScrapeTime | number | tr_tracker_stat
| scrape | string | tr_tracker_stat
| scrapeState | number | tr_tracker_stat
| seederCount | number | tr_tracker_stat
| tier | number | tr_tracker_stat
-------------------+-------------------------+------------+
wanted | an array of tr_info.fileCount | tr_info
| 'booleans' true if the corresponding |
| file is to be downloaded. |
-------------------+--------------------------------------+
webseeds | an array of strings: |
+-------------------------+------------+
| webseed | string | tr_info
+-------------------------+------------+
*
* @param array fields An array of return fields
* @param int|array ids A list of transmission torrent ids
* 示例 Example:
Say we want to get the name and total size of torrents #7 and #10.
请求 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
}
*
* @return mixed
* @throws ClientException
*/
public function get($ids = [], $fields = [])
{
$default = ["id", "name", "status", "doneDate", "haveValid", "totalSize"];
if (!is_array($ids)) {
$ids = array($ids);
}
if (empty($fields)) {
$fields = $default;
} else {
$fields = is_array($fields) ? array_merge($default, $fields) : $default;
}
$request = array(
"fields" => $fields,
"ids" => $ids
);
return $this->request("torrent-get", $request);
}
/**
* 设置一个或多个种子的参数
* Set properties on one or more torrents, available fields are:
* string | value type & description
----------------------+-------------------------------------------------
"bandwidthPriority" | number this torrent's bandwidth tr_priority_t
"downloadLimit" | number maximum download speed (KBps)
"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)
"queuePosition" | number position of this torrent in its queue [0...n)
"seedIdleLimit" | number torrent-level number of minutes of seeding inactivity
"seedIdleMode" | number which seeding inactivity to use. See tr_idlelimit
"seedRatioLimit" | double torrent-level seeding ratio
"seedRatioMode" | number which ratio to use. See tr_ratiolimit
"trackerAdd" | array strings of announce URLs to add
"trackerRemove" | array ids of trackers to remove
"trackerReplace" | array pairs of <trackerId/new announce URLs>
"uploadLimit" | number maximum upload speed (KBps)
"uploadLimited" | boolean true if "uploadLimit" is honored
*
* @param array arguments An associative array of arguments to set
* @param int|array ids A list of transmission torrent ids
* @return mixed
* @throws ClientException
*/
public function set($ids = array(), $arguments = array())
{
if (!is_array($ids)) {
$ids = array($ids);
}
if (!isset($arguments['ids'])) {
$arguments['ids'] = $ids;
}
return $this->request("torrent-set", $arguments);
}
/**
* 添加新种子 (URL)
* Add a new torrent
*
* Request arguments:
* key | value type & description
---------------------+-------------------------------------------------
"cookies" | string pointer to a string of one or more cookies.
"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)
*
* 参数内必须包含"filename"或"metainfo"字段,其他字段都可选。
* Either "filename" OR "metainfo" MUST be included.
All other arguments are optional.
*
* @param string $filename 字符串文件名或种子urlThe URL or path to the torrent file
* @param string $save_path Folder to save torrent in
* @param array $extra_options Optional extra torrent options
* @return mixed
* @throws ClientException
*/
public function add($filename, $save_path = '', $extra_options = array())
{
if (!empty($save_path)) {
$extra_options['download-dir'] = $save_path;
}
$extra_options['filename'] = $filename;
return $this->request("torrent-add", $extra_options);
}
/**
* 添加新种子 (元数据)
* Add a torrent using the raw torrent data
* @param string $torrent_metainfo The raw, unencoded contents (metainfo) of a torrent
* @param string $save_path Folder to save torrent in
* @param array $extra_options Optional extra torrent options
* @return mixed
* @throws ClientException
*/
public function add_metainfo($torrent_metainfo, $save_path = '', $extra_options = array())
{
if (!empty($save_path)) {
$extra_options['download-dir'] = $save_path;
}
$extra_options['metainfo'] = base64_encode($torrent_metainfo);
return $this->request("torrent-add", $extra_options);
}
/**
* 变更数据保存目录
* Move local storage location
*
* Request arguments:
string | value type & description
---------------------------------+-------------------------------------------------
"ids" | array torrent list, as described in 3.1
"location" | string the new torrent location
"move" | boolean if true, move from previous location.
| otherwise, search "location" for files
| (default: false)
*
* @param int|array $ids A list of transmission torrent ids
* @param string $target_location The new storage location
* @param boolean $move_existing_data Move existing data or scan new location for available data
* @return mixed
* @throws ClientException
*/
public function move($ids, $target_location, $move_existing_data = true)
{
if (!is_array($ids)) {
$ids = array($ids);
}
$request = array(
"ids" => $ids,
"location" => $target_location,
"move" => $move_existing_data
);
return $this->request("torrent-set-location", $request);
}
/**
* 修改种子文件或目录名称
* Renaming a Torrent's 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
* @return mixed
* @throws ClientException
*/
public function rename($ids, $path, $name)
{
if (!is_array($ids)) {
$ids = array($ids);
}
if (count($ids) !== 1) {
throw new ClientException('A single id is accepted', self::E_INVALIDARG);
}
$request = array(
"ids" => $ids,
"path" => $path,
"name" => $name
);
return $this->request("torrent-rename-path", $request);
}
/**
* Session 请求
* (4.1) Session 参数
* string | value type | description
* ---------------------------------+------------+-------------------------------------
* "alt-speed-down" | number | max global download speed (KBps)
* "alt-speed-enabled" | boolean | true means use the alt speeds
* "alt-speed-time-begin" | number | when to turn on alt speeds (units: minutes after midnight)
* "alt-speed-time-enabled" | boolean | true means the scheduled on/off times are used
* "alt-speed-time-end" | number | when to turn off alt speeds (units: same)
* "alt-speed-time-day" | number | what day(s) to turn on alt speeds (look at tr_sched_day)
* "alt-speed-up" | number | max global upload speed (KBps)
* "blocklist-url" | string | location of the blocklist to use for "blocklist-update"
* "blocklist-enabled" | boolean | true means enabled
* "blocklist-size" | number | number of rules in the blocklist
* "cache-size-mb" | number | maximum size of the disk cache (MB)
* "config-dir" | string | location of transmission's configuration directory
* "download-dir" | string | default path to download torrents
* "download-queue-size" | number | max number of torrents to download at once (see download-queue-enabled)
* "download-queue-enabled" | boolean | if true, limit how many torrents can be downloaded at once
* "dht-enabled" | boolean | true means allow dht in public torrents
* "encryption" | string | "required", "preferred", "tolerated"
* "idle-seeding-limit" | number | torrents we're seeding will be stopped if they're idle for this long
* "idle-seeding-limit-enabled" | boolean | true if the seeding inactivity limit is honored by default
* "incomplete-dir" | string | path for incomplete torrents, when enabled
* "incomplete-dir-enabled" | boolean | true means keep torrents in incomplete-dir until done
* "lpd-enabled" | boolean | true means allow Local Peer Discovery in public torrents
* "peer-limit-global" | number | maximum global number of peers
* "peer-limit-per-torrent" | number | maximum global number of peers
* "pex-enabled" | boolean | true means allow pex in public torrents
* "peer-port" | number | port number
* "peer-port-random-on-start" | boolean | true means pick a random peer port on launch
* "port-forwarding-enabled" | boolean | true means enabled
* "queue-stalled-enabled" | boolean | whether or not to consider idle torrents as stalled
* "queue-stalled-minutes" | number | torrents that are idle for N minuets aren't counted toward seed-queue-size or download-queue-size
* "rename-partial-files" | boolean | true means append ".part" to incomplete files
* "rpc-version" | number | the current RPC API version 当前RPC版本号
* "rpc-version-minimum" | number | the minimum RPC API version supported 支持RPC的最小版本号
* "script-torrent-done-filename" | string | filename of the script to run
* "script-torrent-done-enabled" | boolean | whether or not to call the "done" script
* "seedRatioLimit" | double | the default seed ratio for torrents to use
* "seedRatioLimited" | boolean | true if seedRatioLimit is honored by default
* "seed-queue-size" | number | max number of torrents to uploaded at once (see seed-queue-enabled)
* "seed-queue-enabled" | boolean | if true, limit how many torrents can be uploaded at once
* "speed-limit-down" | number | max global download speed (KBps)
* "speed-limit-down-enabled" | boolean | true means enabled
* "speed-limit-up" | number | max global upload speed (KBps)
* "speed-limit-up-enabled" | boolean | true means enabled
* "start-added-torrents" | boolean | true means added torrents will be started right away
* "trash-original-torrent-files" | boolean | true means the .torrent file of added torrents will be deleted
* "units" | object | see below
* "utp-enabled" | boolean | true means allow utp
* "version" | string | long version string "$version ($revision)"
* ---------------------------------+------------+-----------------------------+
* units | object containing: |
+--------------+--------+------------------+
| speed-units | array | 4 strings: KB/s, MB/s, GB/s, TB/s
| speed-bytes | number | number of bytes in a KB (1000 for kB; 1024 for KiB)
| size-units | array | 4 strings: KB/s, MB/s, GB/s, TB/s
| size-bytes | number | number of bytes in a KB (1000 for kB; 1024 for KiB)
| memory-units | array | 4 strings: KB/s, MB/s, GB/s, TB/s
| memory-bytes | number | number of bytes in a KB (1000 for kB; 1024 for KiB)
+--------------+--------+------------------+
*
* 当前RPC版本号"rpc-version" indicates the RPC interface version supported by the RPC server.
* It is incremented when a new version of Transmission changes the RPC interface.
*
* 支持RPC的最小版本号"rpc-version-minimum" indicates the oldest API supported by the RPC server.
* It is changes when a new version of Transmission changes the RPC interface
* in a way that is not backwards compatible. There are no plans for this
* to be common behavior.
*/
/**
* 获取会话
* Retrieve all session variables
* Method name: "session-get"
Request arguments: an optional "fields" array of keys (see 4.1)
Response arguments: key/value pairs matching the request's "fields"
argument if present, or all supported fields (see 4.1) otherwise.
*
* @returns array of session information
* @throws ClientException
*/
public function sessionGet()
{
return $this->request("session-get", array());
}
/**
* 设置会话
* Set session variable(s)
* Method name: "session-set"
Request arguments: one or more of 4.1's arguments, except: "blocklist-size",
"config-dir", "rpc-version", "rpc-version-minimum",
"version", and "session-id"
Response arguments: none
*
* @param array of session variables to set
* @return mixed
* @throws ClientException
*/
public function sessionSet($arguments)
{
return $this->request("session-set", $arguments);
}
/**
* 会话状态统计
* Retrieve session statistics
*
* Method name: "session-stats"
Request arguments: none
Response arguments:
string | value type
---------------------------+-------------------------------------------------
"activeTorrentCount" | number
"downloadSpeed" | number
"pausedTorrentCount" | number
"torrentCount" | number
"uploadSpeed" | number
---------------------------+-------------------------------+
"cumulative-stats" | object, containing: |
+------------------+------------+
| uploadedBytes | number | tr_session_stats
| downloadedBytes | number | tr_session_stats
| filesAdded | number | tr_session_stats
| sessionCount | number | tr_session_stats
| secondsActive | number | tr_session_stats
---------------------------+-------------------------------+
"current-stats" | object, containing: |
+------------------+------------+
| uploadedBytes | number | tr_session_stats
| downloadedBytes | number | tr_session_stats
| filesAdded | number | tr_session_stats
| sessionCount | number | tr_session_stats
| secondsActive | number | tr_session_stats
*
* @returns array of statistics
* @throws ClientException
*/
public function sessionStats()
{
return $this->request("session-stats", array());
}
/**
* Return the interpretation of the torrent status
*
* @param int The integer "torrent status"
* @returns string The translated meaning
* @return string
*/
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";
}
/**
* 对请求数据预处理
* 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
* @return array|null
*/
protected function cleanRequestData($array)
{
if (!is_array($array) || count($array) == 0) {
return null;
}
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 || $value !== false)) { // 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)) {
$type = mb_detect_encoding($value, 'auto');
if ($type !== 'UTF-8') {
$array[$index] = mb_convert_encoding($value, 'UTF-8');
}
}
}
return $array;
}
/**
* 获取当前Curl对象
* @return Curl
*/
public function curl()
{
return $this->curl;
}
/**
* 执行 rpc 请求
* @param string $method 请求类型/方法, 详见 $this->allowMethods
* @param array $arguments 附加参数, 可选
* @return array
* @throws ClientException
*/
protected function request($method, $arguments = array())
{
if (!is_scalar($method)) {
throw new ClientException('Method name has no scalar value', self::E_INVALIDARG);
}
if (!is_array($arguments)) {
throw new ClientException('Arguments must be given as array', self::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 ClientException('Unable to acquire X-Transmission-Session-Id', self::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
);
$curl = $this->curl;
if (stripos($this->url, 'https://') === 0) {
$curl->setOpt(CURLOPT_SSL_VERIFYPEER, false); // 禁止验证证书
$curl->setOpt(CURLOPT_SSL_VERIFYHOST, 2); // 不检查证书
}
foreach ($header as $key => $value) {
$curl->setHeader($key, $value);
}
$curl->setUserAgent(self::UA);
$curl->setBasicAuthentication($this->username, $this->password);
$curl->post($this->url, $data, true);
$content = $curl->response;
if ($this->debug) {
var_dump($curl->request_headers);
var_dump($curl->response_headers);
var_dump($curl->response);
}
if (!$content) {
$content = array('result' => 'failed');
}
return json_decode($content, true);
}
/**
* 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()
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $this->url);
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_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
$content = curl_exec($ch);
$error_code = curl_errno($ch);
$error_message = curl_error($ch);
$http_status_code = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
if ($this->debug) {
var_dump($http_status_code);
var_dump($error_message);
var_dump($error_code);
var_dump($content);
}
// 401 Invalid username/password
// 409 成功
// 其他 Unexpected response from Transmission RPC
curl_close($ch);
if($content && preg_match("/<code>X-Transmission-Session-Id: (.*?)<\/code>/", $content, $match)) {
$this->session_id = isset($match[1]) ? $match[1] : null;
}
return $this->session_id;
}
/**
* 抽象方法,子类实现
* 获取下载器链接状态
* @throws ClientException
*/
public function status()
{
$rs = $this->sessionStats();
return isset($rs['result']) ? $rs['result'] : 'error';
}
/**
* 抽象方法,子类实现
* 获取所有种子的列表
* @param array $torrentList
* @return array
* array(
* 'hash' => string json,
* 'sha1' => string,
* 'hashString '=> array
* )
* @throws ClientException
*/
public function all(&$torrentList = array())
{
$ids = array();
$fields = array( "id", "status", "name", "hashString", "downloadDir", "torrentFile" );
$res = $this->get($ids, $fields);
if (isset($res['result']) && $res['result'] === 'success') {
// 成功
} else {
// 失败
echo "从客户端获取种子列表失败可能transmission暂时无响应请稍后重试".PHP_EOL;
return array();
}
if (empty($res['arguments']['torrents'])) {
echo "从客户端未获取到数据,请稍后重试!".PHP_EOL;
return array();
}
$res = $res['arguments']['torrents'];
// 过滤,只保留正常做种
$res = array_filter($res, function ($v) {
return isset($v['status']) && $v['status'] === 6;
}, ARRAY_FILTER_USE_BOTH);
if (empty($res)) {
echo "从客户端未获取到正常做种数据,请多保种,然后重试!".PHP_EOL;
return array();
}
// 提取数组hashString
$info_hash = array_column($res, 'hashString');
// 升序排序
sort($info_hash);
$json = json_encode($info_hash, JSON_UNESCAPED_UNICODE);
// 去重 应该从文件读入,防止重复提交
$sha1 = sha1($json);
// 组装返回数据
$hashArray['hash'] = $json;
$hashArray['sha1'] = $sha1;
// 变换数组hashString为键名、目录为键值
$hashArray['hashString'] = array_column($res, "downloadDir", 'hashString');
$torrentList = array_column($res, null, 'hashString');
return $hashArray;
}
}