├── .gitignore ├── License ├── README.md └── myjdapi_class.php /.gitignore: -------------------------------------------------------------------------------- 1 | docs/ 2 | *.sublime-project 3 | *.sublime-workspace 4 | test.php -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Anatoliy Kultenko "tofik" 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 11 | 12 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A PHP wrapper class for My.jdownloader.org API. 2 | 3 | 4 | Using my.jdownloader.org-api-php-class 5 | 6 | First, you need JDownloader 2 Beta installed on your PC or other hardware, 7 | visit http://jdownloader.org/download/offline. 8 | 9 | Setup JDownloader for my.jdownloader.org in Settings/My.JDownloader. 10 | 11 | If you already registered at my.jdownloader.org than simply fill fields 12 | Username/Email, Password and Device Name, otherwise press button "Go to My.JDownloader.org" 13 | and complete registration. 14 | 15 | Now you can initialize the class 16 | 17 | ```php 18 | 23 | ``` 24 | 25 | Connect to my.jdownloader.org 26 | 27 | ```php 28 | connect( "EMAIL", "PASSWORD"); 33 | $j -> setDeviceName("YOUR_DEVICE_NAME"); 34 | ?> 35 | ``` 36 | 37 | or use this method to initialize the class 38 | 39 | ```php 40 | 45 | ``` 46 | 47 | Available methods: connect, reconnect, disconnect, enumerateDevices, 48 | getDirectConnectionInfos, callAction, addLinks, queryLinks and more maybe later. 49 | 50 | Add links to jdownloader and start it 51 | 52 | ```php 53 | addLinks("LINKS", "PACKAGE_NAME"); 58 | ?> 59 | ``` 60 | 61 | ```php 62 | queryLinks(); 67 | ?> 68 | ``` 69 | Copyright (c) 2014 Anatoliy Kultenko "tofik". 70 | 71 | Released under the BSD License, see http://opensource.org/licenses/BSD-3-Clause 72 | -------------------------------------------------------------------------------- /myjdapi_class.php: -------------------------------------------------------------------------------- 1 | api_url = str_replace('http://', 'https://', $this -> api_url); 32 | } 33 | $this -> rid_counter = time(); 34 | $this -> appkey = $appkey; 35 | if( ($email != "") && ($password != "")) { 36 | $res = $this -> connect( $email, $password); 37 | if( $res === false) { 38 | return false; 39 | } 40 | } 41 | 42 | $this -> setDeviceName( $device_name); 43 | } 44 | 45 | public function getVersion() { 46 | return $this -> version; 47 | } 48 | 49 | //Set AppKey name 50 | public function setAppKeyName( $appkey) { 51 | if( !is_null( $appkey) && is_string( $appkey)) { 52 | $this -> appkey = $appkey; 53 | return true; 54 | } 55 | return false; 56 | } 57 | 58 | //Get AppKey name 59 | public function getAppKeyName() { 60 | return $this -> appkey; 61 | } 62 | 63 | 64 | //Set device name 65 | public function setDeviceName( $device_name) { 66 | if( !is_null( $device_name) && is_string( $device_name)) { 67 | $this -> device_name = $device_name; 68 | return true; 69 | } 70 | return false; 71 | } 72 | 73 | //Get device name 74 | public function getDeviceName() { 75 | return $this -> device_name; 76 | } 77 | 78 | // Connect to api.jdownloader.org 79 | // if success - setup loginSecret, deviceSecret, sessiontoken, regaintoken, serverEncryptionToken, deviceEncryptionToken 80 | // input: email, password 81 | // return: true or false 82 | public function connect( $email, $password) { 83 | $this -> loginSecret = $this -> createSecret( $email, $password, $this -> SERVER_DOMAIN); 84 | $this -> deviceSecret = $this -> createSecret( $email, $password, $this -> DEVICE_DOMAIN); 85 | $query = "/my/connect?email=".urlencode( $email)."&appkey=".urlencode( $this -> appkey); 86 | $res = $this -> callServer( $query, $this -> loginSecret); 87 | if( $res === false) { 88 | return false; 89 | } 90 | $content_json = json_decode( $res, true); 91 | $this -> sessiontoken = $content_json["sessiontoken"]; 92 | $this -> regaintoken = $content_json["regaintoken"]; 93 | $this -> serverEncryptionToken = $this -> updateEncryptionToken( $this -> loginSecret, $this -> sessiontoken); 94 | $this -> deviceEncryptionToken = $this -> updateEncryptionToken( $this -> deviceSecret, $this -> sessiontoken); 95 | return true; 96 | } 97 | 98 | // Reconnect to api.jdownloader.org 99 | // if success - setup sessiontoken, regaintoken, serverEncryptionToken, deviceEncryptionToken 100 | // return: true or false 101 | public function reconnect() { 102 | $query = "/my/reconnect?appkey=".urlencode( $this -> appkey)."&sessiontoken=".urlencode( $this -> sessiontoken)."®aintoken=".urlencode( $this -> regaintoken); 103 | $res = $this -> callServer( $query, $this -> serverEncryptionToken); 104 | if( $res === false) { 105 | return false; 106 | } 107 | $content_json = json_decode( $res, true); 108 | $this -> sessiontoken = $content_json["sessiontoken"]; 109 | $this -> regaintoken = $content_json["regaintoken"]; 110 | $this -> serverEncryptionToken = $this -> updateEncryptionToken( $this -> serverEncryptionToken, $this -> sessiontoken); 111 | $this -> deviceEncryptionToken = $this -> updateEncryptionToken( $this -> deviceSecret, $this -> sessiontoken); 112 | return true; 113 | } 114 | 115 | // Disconnect from api.jdownloader.org 116 | // if success - cleanup sessiontoken, regaintoken, serverEncryptionToken, deviceEncryptionToken 117 | // return: true or false 118 | public function disconnect() { 119 | $query = "/my/disconnect?sessiontoken=".urlencode( $this -> sessiontoken); 120 | $res = $this -> callServer( $query, $this -> serverEncryptionToken); 121 | if( $res === false) { 122 | return false; 123 | } 124 | $content_json = json_decode( $res, true); 125 | $this -> sessiontoken = ""; 126 | $this -> regaintoken = ""; 127 | $this -> serverEncryptionToken = ""; 128 | $this -> deviceEncryptionToken = ""; 129 | return true; 130 | } 131 | 132 | // Enumerate Devices connected to my.jdownloader.org 133 | // if success - setup devices 134 | // call getDirectConnectionInfos to setup devices 135 | // return: true or false 136 | public function enumerateDevices() { 137 | $query = "/my/listdevices?sessiontoken=".urlencode( $this -> sessiontoken); 138 | $res = $this -> callServer( $query, $this -> serverEncryptionToken); 139 | if( $res === false) { 140 | return false; 141 | } 142 | $content_array = json_decode( $res, true); 143 | $this -> devices = $content_array["list"]; 144 | $res = $this -> getDirectConnectionInfos(); 145 | if( $res === false) { 146 | return false; 147 | } 148 | return true; 149 | } 150 | 151 | // Call action "/device/getDirectConnectionInfos" for each devices 152 | // if success - setup devices with infos 153 | // return: true or false 154 | public function getDirectConnectionInfos() { 155 | foreach( $this -> devices as $i => &$ivalue) { 156 | $res = $this -> callAction( "/device/getDirectConnectionInfos"); 157 | if( $res === false) { 158 | return false; 159 | } 160 | $content_array = json_decode( $res, true); 161 | $this -> devices[$i]["infos"] = $content_array["data"]["infos"]; 162 | } 163 | return true; 164 | } 165 | 166 | // Send links to device using action /linkgrabberv2/addLinks 167 | // input: device - name of device, links - array or string of links, package_name - custom package name 168 | // {"url":"/linkgrabberv2/addLinks", 169 | // "params":["{\"priority\":\"DEFAULT\",\"links\":\"YOURLINK\",\"autostart\":true, \"packageName\": \"YOURPKGNAME\"}"], 170 | // "rid":YOURREQUESTID,"apiVer":1} 171 | public function addLinks( $links, $package_name = null) { 172 | if( !is_array( $this -> devices)) { 173 | $this -> enumerateDevices(); 174 | } 175 | if( is_array( $links)) { 176 | $links = implode( ",", $links); 177 | } 178 | $params = '\"priority\":\"DEFAULT\",\"links\":\"'.$links.'\",\"autostart\":true, \"packageName\": \"'.$package_name.'\"'; 179 | $res = $this -> callAction( "/linkgrabberv2/addLinks", $params); 180 | if( $res === false) { 181 | return false; 182 | } 183 | return true; 184 | } 185 | 186 | // Retrive links 187 | public function queryLinks( $params = []) { 188 | //taken from: https://docs.google.com/document/d/1IGeAwg8bQyaCTeTl_WyjLyBPh4NBOayO0_MAmvP5Mu4/edit# (LinkQueryStorable) 189 | $params_default = [ 190 | "bytesTotal" => true, 191 | "comment" => true, 192 | "status" => true, 193 | "enabled" => true, 194 | "maxResults" => -1, 195 | "startAt" => 0, 196 | "packageUUIDs" => null, 197 | "host" => true, 198 | "url" => true, 199 | "bytesLoaded" => true, 200 | "speed" => true, 201 | "eta" => true, 202 | "finished" => true, 203 | "priority" => true, 204 | "running" => true, 205 | "skipped" => true, 206 | "extractionStatus" => true 207 | ]; 208 | 209 | $params = array_merge( $params_default, $params); 210 | 211 | $res = $this -> callAction( "/downloadsV2/queryLinks", $params); 212 | return $res; 213 | } 214 | // Make a call to my.jdownloader.org 215 | // input: query - path+params, key - key for encryption, params - additional params 216 | // return: result from server or false 217 | private function callServer( $query, $key, $params = false) { 218 | if( $params != "") { 219 | if( $key != "") { 220 | $params = $this -> encrypt( $params, $key); 221 | } 222 | $rid = $this -> rid_counter; 223 | } else { 224 | $rid = $this -> getUniqueRid(); 225 | } 226 | if( strpos( $query, "?") !== false) { $query = $query."&"; } else { $query = $query."?"; } 227 | $query = $query."rid=".$rid; 228 | $signature = $this -> sign( $key, $query); 229 | $query = $query."&signature=".$signature; 230 | $url = $this -> api_url.$query; 231 | if( $params != "") { 232 | $res = $this -> postQuery( $url, $params, $key); 233 | } else { 234 | $res = $this -> postQuery( $url, "", $key); 235 | } 236 | if( $res === false) { 237 | return false; 238 | } 239 | $content_json = json_decode( $res, true); 240 | if( $content_json["rid"] != $this -> rid_counter) { 241 | return false; 242 | } 243 | return $res; 244 | } 245 | 246 | // Make a call to API function on my.jdownloader.org 247 | // input: device_name - name of device to send action, action - action pathname, params - additional params 248 | // return: result from server or false 249 | public function callAction( $action, $params = false) { 250 | if( !is_array( $this -> devices)) { 251 | $this -> enumerateDevices(); 252 | } 253 | 254 | if( !is_array( $this -> devices) || ( count( $this -> devices) == 0)) { 255 | return false; 256 | } 257 | 258 | foreach( $this -> devices as $i => &$ivalue) { 259 | if( $this -> devices[$i]["name"] == $this->getDeviceName()) { 260 | $device_id = $this -> devices[$i]["id"]; 261 | } 262 | } 263 | if( !isset( $device_id)) { 264 | return false; 265 | } 266 | $query = "/t_".urlencode( $this -> sessiontoken)."_".urlencode( $device_id).$action; 267 | if( $params != "") { 268 | if(is_array($params)) { 269 | $params = str_replace('"', '\"', substr(json_encode($params),1,-1)); 270 | } 271 | $json_data = '{"url":"'.$action.'","params":["{'.$params.'}"],"rid":'.$this -> getUniqueRid().',"apiVer":'.$this -> apiVer.'}'; 272 | } else { 273 | $json_data = '{"url":"'.$action.'","rid":'.$this -> getUniqueRid().',"apiVer":'.$this -> apiVer.'}'; 274 | } 275 | $json_data = $this -> encrypt( $json_data, $this -> deviceEncryptionToken); 276 | $url = $this -> api_url.$query; 277 | $res = $this -> postQuery( $url, $json_data, $this -> deviceEncryptionToken); 278 | if( $res === false) { 279 | return false; 280 | } 281 | $content_json = json_decode( $res, true); 282 | if( $content_json["rid"] != $this -> rid_counter) { 283 | return false; 284 | } 285 | return $res; 286 | } 287 | 288 | // Genarate new unique rid 289 | // return new rid_counter 290 | public function getUniqueRid() { 291 | $this -> rid_counter++; 292 | return $this -> rid_counter; 293 | } 294 | 295 | // Return current rid_counter 296 | public function getRid() { 297 | return $this -> rid_counter; 298 | } 299 | 300 | private function createSecret( $username, $password, $domain) { 301 | return hash( "sha256", strtolower( $username) . $password . strtolower( $domain), true); 302 | } 303 | 304 | private function sign( $key, $data) { 305 | return hash_hmac( "sha256", $data, $key); 306 | } 307 | 308 | private function decrypt( $data, $iv_key) { 309 | $iv = substr( $iv_key, 0, strlen( $iv_key)/2); 310 | $key = substr( $iv_key, strlen( $iv_key)/2); 311 | return openssl_decrypt( base64_decode( $data), "aes-128-cbc", $key, OPENSSL_RAW_DATA, $iv); 312 | } 313 | 314 | private function encrypt( $data, $iv_key) { 315 | $iv = substr( $iv_key, 0, strlen( $iv_key)/2); 316 | $key = substr( $iv_key, strlen( $iv_key)/2); 317 | return base64_encode( openssl_encrypt( $data, "aes-128-cbc", $key, OPENSSL_RAW_DATA, $iv)); 318 | } 319 | 320 | private function updateEncryptionToken( $oldToken, $updateToken) { 321 | return hash( "sha256", $oldToken.pack( "H*", $updateToken), true); 322 | } 323 | 324 | // postQuery( $url, $postfields, $iv_key) 325 | // Make Get or Post Request to $url ( $postfields) 326 | // Send Payload data if $postfields not null 327 | // return plain response or decrypted response if $iv_key not null 328 | private function postQuery( $url, $postfields = false, $iv_key = false) { 329 | $ch = curl_init(); 330 | curl_setopt( $ch, CURLOPT_URL, $url); 331 | curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true); 332 | if( $postfields) { 333 | $headers[] = "Content-Type: application/aesjson-jd; charset=utf-8"; 334 | curl_setopt( $ch, CURLOPT_POST, true); 335 | curl_setopt( $ch, CURLOPT_POSTFIELDS, $postfields); 336 | curl_setopt( $ch, CURLOPT_HEADER, true); 337 | curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers); 338 | } 339 | $response = array(); 340 | $response["text"] = curl_exec( $ch); 341 | $response["info"] = curl_getinfo( $ch); 342 | $response["code"] = $response["info"]["http_code"]; 343 | if( $response["code"] != 200) { 344 | return false; 345 | } 346 | if( $postfields) { 347 | $response["body"] = substr( $response["text"], $response["info"]["header_size"]); 348 | } else { 349 | $response["body"] = $response["text"]; 350 | } 351 | if( $iv_key) { 352 | $response["body"] = $this -> decrypt( $response["body"], $iv_key); 353 | } 354 | curl_close( $ch); 355 | return $response["body"]; 356 | } 357 | } 358 | --------------------------------------------------------------------------------