mirror of
https://gitee.com/ledc/IYUUAutoReseed
synced 2025-04-26 12:23:24 +00:00
352 lines
11 KiB
PHP
352 lines
11 KiB
PHP
<?php
|
||
namespace IYUU\Client\qBittorrent;
|
||
|
||
use Curl\Curl;
|
||
use IYUU\Client\AbstractClient;
|
||
|
||
/**
|
||
* https://github.com/qbittorrent/qBittorrent/wiki/Web-API-Documentation
|
||
*/
|
||
class qBittorrent extends AbstractClient
|
||
{
|
||
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("qBittorrent 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 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. '"' . $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
|
||
*/
|
||
private 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();
|
||
}
|
||
|
||
/**
|
||
* 抽象方法,子类实现
|
||
*/
|
||
public function getList(&$move = 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');
|
||
return $hashArray;
|
||
}
|
||
|
||
/**
|
||
* 抽象方法,子类实现
|
||
*/
|
||
public function delete($hash='', $deleteFiles = false)
|
||
{
|
||
return $this->postData('torrent_delete', ['hashes' => $hash, 'deleteFiles' => $deleteFiles ? 'true':'false']);
|
||
}
|
||
}
|