Archived

This topic is now archived and is closed to further replies.

Miyanokouji

PHP API for the WebUI

Recommended Posts

Great code you guys!

I was goind to give some fixes, but m69bv3 already did (using substr instead os substring and add-url instead of get-url), great work!

One sugestion I have is to add urlencode when adding a http torrent. It's better and safer.

Share this post


Link to post
Share on other sites
public function addTorrent($filename,&$estring = false) {

...

curl_setopt($ch,CURLOPT_URL,"http://".$this->host."/gui/?action=get-url&s=".urlencode($filename));

...

Do you mean "add-url" when you write "get-url"?

Edit: Er, I guess you copied m69bv3's code incorrectly...?


//$file is a number between 0 and the number of files in the torrent minus one. ( I think. might be 1-based)

...

public function setPriority($hash,$files,$priority) {

$filenums = "";

if(is_array($files)) {

foreach($files as $value) {

$filenums.="&f=".$value;

}

} else {

$filenums = "&hash=".$file;

}

$ch = curl_init();

curl_setopt($ch,CURLOPT_URL,"http://".$this->host."/gui/?action=setprio&hash=$hash&p=".$priority."&f=".$filenums);

...

The file index is zero-based :P Regarding the $filenums variable... it sees rather odd how you're setting the URL up. When you check whether $files is an array, and it hits the else block, you should be setting $filenums to "&f=".$file, not "&hash=".$file. Also, when you actually call curl_setopt() on CURLOPT_URL, you don't need the extra "&f=" at the end (since it's already in $filenums).

Edit: Oh, nowotny already fixed it here... Not correctly fixed in the main post?


public function is_online() {

...

$ret = curl_exec($ch);

if($ret !== false) {

$ret = true;

}

return $ret;

}

Would it not be easier to simply say

return (curl_exec($ch) !== false);

?


I hesitate to modify the first post for Miyanokouji because I'm only looking at it from the outside without much of any PHP experience (so I'm not sure if my suggestions/changes actually work).

Share this post


Link to post
Share on other sites

Look at the API documentation on the ?list=1 output, and you'll find that it's rather simple. From the returned/parsed JSON, you'll get a dictionary (call it $list), then you simply access $list['torrents'] (or however you access hashtable data in PHP). From there, loop though the list, checking the first element in each item in the list to find the corresponding hash you're looking for ($list['torrents'][$i][0] or something).

The looping is somewhat cumbersome, so Directrix has already proposed a change to the ?list=1 output to make it so that $list['torrents'] is actually a dictionary whose keys are the hashes (so you'd be able to access the data directly using $list['torrents'][$hash] or whatever). That hasn't been implemented yet, though.

Share this post


Link to post
Share on other sites

Can anyone explain to me how to use the addTorrent function in the Web_api.

Lets say i have a list of links, how can i add one of the links to uTorrent when i click on it?

Share this post


Link to post
Share on other sites

Indeed. The PHP API code is very simple to understand -- even for someone like me, who has an absolutely minimal amount of experience with PHP. For someone who's supposed to have coded the mUI, shouldn't this be cake?

@mofle: To answer your question... If the uTorrent object is called $torrent, and you have an array of URLs called $urls, then foreach ($urls as $url) $torrent->addTorrent($url); is how you would add the torrents.

@Lord Alderaan: Since you probably have plenty of experience with PHP... Are the points I brougt up earlier correct and safe to put into the first post?

Share this post


Link to post
Share on other sites

Just a few thoughts:

Any reason why anybody would need multiple uTorrent objects? I'd make uTorrent a singleton. (atleast add a constructor)

The are a few too many curl* blocks (imo) which could be made into a method, eg.:

private function makeRequest($qs, &$ret = null, $options = array()) {

$ch = curl_init();

curl_setopt_array($ch, $options);

curl_setopt($ch, CURLOPT_URL, "http://" . $this->host . "/gui/" . $qs);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

curl_setopt($ch, CURLOPT_USERPWD, $this->user.":".$this->pass);

$ret = curl_exec($ch);

curl_close($ch);

}

then getTorrents would look like:

public function getTorrents() {

$ret = "";

$this->makeRequest("?list=1", $ret);

$obj = json_decode($ret, true);

$torrents = $obj['torrents'];

return $torrents;

}

(makeRequest could return the results instead of setting a reference aswell)

You could store the labels from getTorrent in an array and then return that array in getLabels instead of making another ?list=1 request.

The foreach-loops could be replaced with this one-liner:

$hashes = "&hash=" . implode("&hash=", is_array($hash) ? $hash : array($hash));

Share this post


Link to post
Share on other sites

@Ultima:

Your right about the add-url thing.

And nowotny fix of the priority thing should also be implemented.

You should definitely update the first post with those.

About the return thing. I usually don't make statements like that inside a return function but I agree that would be sleeker and have the same result.

I also like Directx's suggestions but that would require a minor overhaul.

I myself haven't delved or even used this class though. Hadn't found a need for it myself but I'm thinking about writing a shell in php that will allow multi-user stuff etc when 1.8 and the new webui are out.

Share this post


Link to post
Share on other sites

I've fixed the "big" problems in the first post. Didn't bother backporting tweaks (like Directrix's suggested use of implode, or my is_online() change), since I don't want to potentially break what currently works without any real testing of my own.

Onto something mildly more interesting...

<?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 = "http://%s:%s/gui/%s";

// member variables
public $host;
public $port;
public $user;
public $pass;

public $token;

// constructor
function __construct($host = "", $port = "", $user = "", $pass = "") {
$this->host = $host;
$this->port = $port;
$this->user = $user;
$this->pass = $pass;

if (!$this->getToken($this->token)) {
//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()) {
if (!empty($this->token)) {
// Check if we have a ?
if (substr($request, 0, 1) == '?')
$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, $this->port, $request));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, $this->user.":".$this->pass);

$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, token is stored in $token by-ref argument
public function getToken(&$token) {
$output = $this->makeRequest('token.html', false);

if (preg_match('/<div id=\'token\'.+>(.*)<\/div>/', $output, $m)) {
$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'];
}

// 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|", "|".$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);
}
}

?>

Changelog

[2009-08-18]

  • Added: Rudimentary token authentication support (courtesy of saivert)

[2008-04-10]

  • Fixed: torrentAdd() wasn't making the correct request ("action=..." instead of "?action=...")
  • Changed: __construct($host, $port, $user, $pass)
  • Changed: Constructor parameters are now optional
  • Changed: is_online() makes sure server is running and webUI is actually enabled

[2008-04-09]

  • Added: __constructor($host, $user, $pass)
  • Added: addTorrent() now calls add-url for https:// and file:// too
  • Added: getBuild() (works only with µTorrent 1.8.x)
  • Fixed: addTorrent() correctly calls add-url now instead of get-url
  • Fixed: setPriority() had minor bugs
  • Fixed: addTorrent() uses substr() instead of substring() (which doesn't exist)
  • Changed: *Torrent() --> torrent*() (basically, I renamed a bunch of the functions, like addTorrent --> torrentAdd, or forcestartTorrent --> torrentForceStart). I changed the function names because... well, it's more organized this way, since the function naming actually has some kind of prefix scheme.
  • Changed: All get*() functions now return the relevant node in the JSON output rather than the full JSON output (which happened in some cases, like getFiles(), getProperties(), or getSettings())
  • Changed: Major code cleanup (almost a complete rewrite)
  • Changed: Some comments were removed regarding structure of returned data... the information can always be found here. I'll consider re-adding more detailed comments after this code gets a bit more feedback.
  • Changed: The foreach loops were changed to implode() statements

This code could probably do with a bit more error checking in various places, but uh, I don't think it's any worse off than the original version on that front. At any rate, I might've forgotten to list a few fixes/additions I made along the way. I haven't tested ANY of this, by the way... I'm going purely by instinct! I've finally decided to install XAMPP... And amazingly, it worked properly (well, besides torrentAdd(), but that's fixed now) xD

Feedback, feedback, feedback!


Development Discussion

I don't think the class should be made singleton... There's no real need to artificially limit the number of uTorrent objects that can be made. I mean, what if the user wants to control multiple web UIs simultaneously? At any rate, you'll notice I've added a constructor. Wewt?!

I never got around to modifying getTorrents() to make it return both the labels and torrents... I think that if we're going to go that route, the function might as well be renamed to getList()... Comments? Also, should there be a way to get an updated list by inputting a CID/torrentc value? getList($cid = 0) or something?

As you'll notice, there are several TODO's in the comments... For setProperty() and setSetting(), I'm not really sure how best to proceed, since I don't really like the prospect of attempting to detect/interpret the parameters... Should I simply create two new functions that take arrays as parameters instead (like setProperties() and setSettings())?

I think I should change makeRequest() to makeGETRequest(), and then create a makePOSTRequest() as well. It's currently possible to peform POST requests with makeRequest() because of Directrix's foresight (the addition of the $option parameter). The only reason I think it should be further split is because I feel like the code should be made a bit more modular, so that people who don't have cURL installed can simply replace the makeGETRequest() and makePOSTRequest() methods with something that works on their server...

Opinions?

Edit: lol wow, didn't realize Rexxars already implemented many of these changes... approximately four months ago.

Interestingly (or not?), I also considered writing a function not too different from his processHashArgument() function (albeit, a bit more generalized in that I'd also pass it the glue), just because writing

"&hash=".implode("&hash=", is_array($hash) ? $hash : array($hash))

in so many places is somewhat annoying... [Edit: Done with that... paramImplode()]

*shrug* I guess there's only so many ways one could do this...

Share this post


Link to post
Share on other sites

grr :D

Nice job falks. Can I ask sheduler on/off option or function?

I have created a script that pings over the local LAN, and it would turn off sheduler if most of the PCs are offline. And of course it turns the sheduler on if others may want to use the internet, but it wont work without the sheduler function :)

Share this post


Link to post
Share on other sites

You can use this to make your code smaller and neater. I might consider porting your code over to use this class anyway. Also note that this is version 0.5.0, that is due to the fact that I have not implemented any of the multi functions found in cURL as of yet. Once I do find a use for them I will implement them tho and update them here.

<?php
/*
** @package cURL Class
** @since 2008-06-10 05:30
** @author Mark 'Dygear' Tomlin
** @license MIT License (http://opensource.org/licenses/mit-license.php)
** @copyright Copyright (C) 2006, Mark 'Dygear' Tomlin
** @version 0.5.0
*/

if (($cURL = new cURL()) === FALSE) {
trigger_error('Unable to Start new cURL session!');
// die(); // Might not want to end it there ...
}

class cURL {
# Initialize a cURL session
function __construct($url = NULL) {
if (!extension_loaded('curl') && (!dl('php_curl.dll') || !dl('curl.so'))) {
trigger_error('cURL: Extension Not Loaded!');
return FALSE;
}
if (!function_exists('curl_setopt')) {
trigger_error('cURL: Functions Not Loaded!');
return FALSE;
}
$this->self = curl_init($url);
}
# Copy a cURL handle along with all of its preferences
function copyHandle() {
return curl_copy_handle($this->self);
}
# Return the last error number
function errno() {
return curl_errno($this->self);
}
# Return a string containing the last error for the current session
function error() {
return curl_error($this->self);
}
# Perform a cURL session
function exec() {
return curl_exec($this->self);
}
# Get information regarding a specific transfer
function getInfo($opt = NULL) {
return curl_getinfo($this->self, $opt);
}

# Set an option for a cURL transfer
function setOpt($option, $value) {
return curl_setopt($this->self, $option, $value);
}
# Set multiple options for a cURL transfer
function setOptArray($options) {
if (!function_exists('curl_setopt_array')) {
function curl_setopt_array(&$ch, $curl_options) {
foreach ($curl_options as $option => $value) {
if (!$this->setOpt($ch, $option, $value)) {
return FALSE;
}
}
return TRUE;
}
}
return curl_setopt_array($this->self, $options);
}
#
function version($age = NULL) {
return curl_version($age);
}
# Close a cURL session
function __destruct() {
if (isset($this->self)) {
return curl_close($this->self);
}
}
}

?>

Ah, I see now that your using a function to handle all of the querys, much better idea! Well, the code is there, take it or leave it!

Share this post


Link to post
Share on other sites

Re. I wrote a simple code to schedule your torrents, and added a feature.

Most of us (here) have shared internet connection so we have to care about the "others" who may want to use the bandwidth at daylight, so i decided to write a scheduler with network watcher. It simply ping the pc's on the network and if anybody is online (when download is "forbidden") stops the torrents.

Uploaded the hole script (compressed into one file):

http://abydos.uw.hu/dwn/scheduler.rar

The $ips array contains the ips of the CP's.

The $allowed array contains the time slices.

I've split the timeline to 48 pieces (to half hours), for example

"Sunday" =>array(0,0,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1)

means that from 1:00-6:59, 23:00-(monday)15:30 download is allowed

$label_stop="something"; means the torrents that you dont want to start

$label_seed="seedme"; this torrents will start after finished (after reachin 100.0%)

You can enable/disable the script by renaming/deleting a file ("enable" without extension).

And it logs if anything happen (stop,start,disabled)

To run the script (scheduled :P) you only have to do is create a txt and place this line into it:

X:\...\php\php Y:\...\htdocs\schedule.php

than modify the extension to .bat and add it into the windows scheduler.

(dont forget to create "logs" and "sets" directory near the script, and of course put your enable_* and ip_port_set.php files into the "sets" dir.)

I hope its useful.

edit: added the most important, if download isnt allowed but nobody is online we can start the torrent(s).

TODO:

-?

Share this post


Link to post
Share on other sites

If you feel you've done a good job adding token support simply post the code here. I don't think anyone would mind.

This is a collaborative work isn't it?

This is my take on it:

getToken method:

    // gets token, returns true on success, token is stored in $token by-ref argument
public function getToken(&$token) {
$output = $this->makeRequest('token.html', false);

if (preg_match('/<div id=\'token\'.+>(.*)<\/div>/', $output, $m)) {
$token = $m[1];
return true;
}
return false;
}}

Modified constructor:

    // constructor
function __construct($host = "", $port = "", $user = "", $pass = "") {
$this->host = $host;
$this->port = $port;
$this->user = $user;
$this->pass = $pass;

if (!$this->getToken($this->token)) {
//handle error here, don't know how to best do this yet
die('could not get token');
}
}

Modified makeRequest method:

    // performs request
private function makeRequest($request, $decode = true, $options = array()) {
$ch = curl_init();

curl_setopt_array($ch, $options);
if (!empty($this->token)) {
// Check if we have a ?
if (substr($request, 0, 1) == '?')
$request = preg_replace('/^\?/', '?token='.$this->token . '&', $request);
}
//print "<code>$request</code>"; //DEBUG
curl_setopt($ch, CURLOPT_URL, sprintf(self::$base, $this->host, $this->port, $request));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERPWD, $this->user.":".$this->pass);

$req = curl_exec($ch);
curl_close($ch);

return ($decode ? json_decode($req, true) : $req);
}

Also I made a package with the updated uTorrent class and a sample php script that demonstrates how to use it.

Contact me for the download.

Share this post


Link to post
Share on other sites

I am having trouble with this on my website and to my local pc... I know that I can access my normal webUI from outside the network (using my iphone internet) but when I try and host this on my website, it just doesn't want to connect. I know that the IP/port/username/password are all correct.

Also I am using the script from the main post, which works fine locally but I would like this to be remote.

Can somebody help me out with this?

Share this post


Link to post
Share on other sites