Skip to content

Commit f53f47d

Browse files
committed
Refactor & simplify some of the changes to support direct connection with RemoteAPI.
1 parent 65398b1 commit f53f47d

File tree

4 files changed

+87
-81
lines changed

4 files changed

+87
-81
lines changed

DIRECT.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ This is an extension of myjdapi to allow a direct device access without My.JDown
66

77
My.JDowloader manages JDownloader devices and myjdapi provides a python interface to access them.
88
If possible My.JDowloader will push a direct connection to myjdapi which will be used consequently.
9-
If enabled this JDownloader api will be available locally. The direct access modification will make
9+
If enabled this JDownloader api will be available locally. The direct access modification will make
1010
it possible to use the local api without any interaction with My.Downloader at all.
1111

1212
## Implemetation
1313

1414
In myjdapi all connetions will be managed by an instance of Myjdapi. To use myjdapi locally Myjdapi needs to be extended to allow local connextions.
1515

16-
`Myjdapi().connect_device(ip, port, _type='jd', username=None, password=None, timeout=None)` will create
17-
a local connection to a given device and it will make all modifications to the myjdapi instance to support
16+
`Myjdapi().connect_device(ip, port, _type='jd', username=None, password=None, timeout=None)` will create
17+
a local connection to a given device and it will make all modifications to the myjdapi instance to support
1818
the direct communication with the local device.
1919

2020
Even so the interface supports a user name and password it is not implemented yet.
@@ -29,16 +29,18 @@ After myjdapi has established the connection the device can be querried as usual
2929
```python
3030
import myjdapi
3131
host = 'localhost'
32-
port = 3128
32+
port = 3128 # Optional, by default is 3128
3333
# We need an instance of Myjdapi() but no application key is required
3434
jd = myjdapi.Myjdapi()
3535
# connect directly to the device
36-
jd.connect_device(host,port,timeout=10)
36+
jd.connect_device(host,port,timeout=10) # Timeout is optional.
3737
# The device can be accessed using the host name or ip address.
3838
device = jd.get_device(host)
3939
# Or the device can be accessed by using the device id "direct".
4040
device = jd.get_device(device_id="direct")
41+
# Or the device can be accessed by using it without parameters to obtain the single device.
42+
device = jd.get_device()
4143
```
4244

43-
Once the connection to the local device is established myjdapi will not connect to My.JDownloader any more.
45+
Once the connection to the local device is established myjdapi will not connect to My.JDownloader any more.
4446
The connection is permanent and will not be refreshed any more.

myjdapi/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,4 @@
3434
MYJDUnknownException,
3535
)
3636

37-
__version__ = "1.1.8"
37+
__version__ = "1.1.9"

myjdapi/myjdapi.py

Lines changed: 77 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -966,7 +966,6 @@ def __init__(self, jd, device_dict):
966966
self.extensions = Extension(self)
967967
self.dialogs = Dialog(self)
968968
self.update = Update(self)
969-
self.jd = Jd(self)
970969
self.system = System(self)
971970
self.__direct_connection_info = None
972971
self.__refresh_direct_connections()
@@ -975,9 +974,15 @@ def __init__(self, jd, device_dict):
975974
self.__direct_connection_consecutive_failures = 0
976975

977976
def __refresh_direct_connections(self):
978-
infos=self.myjd.get_direct_Connection_infos(self.__action_url())
979-
if infos:
980-
self.__update_direct_connections(infos)
977+
if self.myjd.get_connection_type() == "remoteapi":
978+
return
979+
response = self.myjd.request_api("/device/getDirectConnectionInfos",
980+
"POST", None, self.__action_url())
981+
if response is not None \
982+
and 'data' in response \
983+
and 'infos' in response["data"] \
984+
and len(response["data"]["infos"]) != 0:
985+
self.__update_direct_connections(response["data"]["infos"])
981986

982987
def __update_direct_connections(self, direct_info):
983988
"""
@@ -1017,7 +1022,11 @@ def action(self, path, params=(), http_action="POST"):
10171022
/example?param1=ex&param2=ex2 [("param1","ex"),("param2","ex2")]
10181023
:param postparams: List of Params that are send in the post.
10191024
"""
1020-
action_url = self.__action_url()
1025+
1026+
if self.myjd.get_connection_type() == "remoteapi":
1027+
action_url = None
1028+
else:
1029+
action_url = self.__action_url()
10211030
if not self.__direct_connection_enabled or self.__direct_connection_info is None \
10221031
or time.time() < self.__direct_connection_cooldown:
10231032
# No direct connection available, we use My.JDownloader api.
@@ -1070,11 +1079,7 @@ def action(self, path, params=(), http_action="POST"):
10701079
return response['data']
10711080

10721081
def __action_url(self):
1073-
if self.myjd.get_session_token():
1074-
return "/t_" + self.myjd.get_session_token() + "_" + self.device_id
1075-
else:
1076-
return None
1077-
1082+
return "/t_" + self.myjd.get_session_token() + "_" + self.device_id
10781083

10791084
class Myjdapi:
10801085
"""
@@ -1100,8 +1105,8 @@ def __init__(self):
11001105
self.__server_encryption_token = None
11011106
self.__device_encryption_token = None
11021107
self.__connected = False
1103-
self.__dict_as_str = True
1104-
self.__timeout=3
1108+
self.__timeout = 3
1109+
self.__connection_type = "myjd" # myjd -> MyJdownloader API, remoteapi -> Deprecated Direct RemoteAPI connection.
11051110

11061111
def get_session_token(self):
11071112
return self.__session_token
@@ -1139,7 +1144,7 @@ def __update_encryption_tokens(self):
11391144
Updates the server_encryption_token and device_encryption_token
11401145
11411146
"""
1142-
if self.__connected and not self.__session_token:
1147+
if self.__connection_type == "remoteapi":
11431148
return
11441149
if self.__server_encryption_token is None:
11451150
old_token = self.__login_secret
@@ -1170,14 +1175,13 @@ def __decrypt(self, secret_token, data):
11701175
:param secret_token:
11711176
:param data:
11721177
"""
1173-
if secret_token:
1174-
init_vector = secret_token[:len(secret_token) // 2]
1175-
key = secret_token[len(secret_token) // 2:]
1176-
decryptor = AES.new(key, AES.MODE_CBC, init_vector)
1177-
decrypted_data = UNPAD(decryptor.decrypt(base64.b64decode(data)))
1178-
return decrypted_data
1179-
else:
1178+
if self.__connection_type == "remoteapi":
11801179
return data.encode('utf-8')
1180+
init_vector = secret_token[:len(secret_token) // 2]
1181+
key = secret_token[len(secret_token) // 2:]
1182+
decryptor = AES.new(key, AES.MODE_CBC, init_vector)
1183+
decrypted_data = UNPAD(decryptor.decrypt(base64.b64decode(data)))
1184+
return decrypted_data
11811185

11821186
def __encrypt(self, secret_token, data):
11831187
"""
@@ -1186,15 +1190,14 @@ def __encrypt(self, secret_token, data):
11861190
:param secret_token:
11871191
:param data:
11881192
"""
1189-
if secret_token:
1190-
data = PAD(data.encode('utf-8'))
1191-
init_vector = secret_token[:len(secret_token) // 2]
1192-
key = secret_token[len(secret_token) // 2:]
1193-
encryptor = AES.new(key, AES.MODE_CBC, init_vector)
1194-
encrypted_data = base64.b64encode(encryptor.encrypt(data))
1195-
return encrypted_data.decode('utf-8')
1196-
else:
1193+
if self.__connection_type == "remoteapi":
11971194
return data
1195+
data = PAD(data.encode('utf-8'))
1196+
init_vector = secret_token[:len(secret_token) // 2]
1197+
key = secret_token[len(secret_token) // 2:]
1198+
encryptor = AES.new(key, AES.MODE_CBC, init_vector)
1199+
encrypted_data = base64.b64encode(encryptor.encrypt(data))
1200+
return encrypted_data.decode('utf-8')
11981201

11991202
def update_request_id(self):
12001203
"""
@@ -1218,8 +1221,8 @@ def connect(self, email, password):
12181221
self.__server_encryption_token = None
12191222
self.__device_encryption_token = None
12201223
self.__devices = None
1221-
self.__device_connection = None
12221224
self.__connected = False
1225+
self.__connection_type = "myjd"
12231226

12241227
self.__login_secret = self.__secret_create(email, password, "server")
12251228
self.__device_secret = self.__secret_create(email, password, "device")
@@ -1234,43 +1237,40 @@ def connect(self, email, password):
12341237
self.update_devices()
12351238
return response
12361239

1237-
def connect_device(self, ip, port, _type='jd', username=None, password=None, timeout=None):
1238-
"""Establish a direct connection to api of a device
1240+
def direct_connect(self, ip, port=3128, timeout=3):
1241+
"""
1242+
Direct connect to a single device/app instance using the deprecated RemoteAPI.
1243+
This RemoteAPI has to be enabled on JDownloader beforehand.
1244+
Beaware this connection is not authenticated nor encrypted, so do not enable
1245+
it publicly.
12391246
1240-
:param ip: ip of the divice
1241-
:param port: port of the divice
1247+
:param ip: ip of the device
1248+
:param port: port of the device, 3128 by default.
1249+
:param port: optional timeout of the connection, 3 seconds by default.
12421250
:returns: boolean -- True if succesful, False if there was any error.
12431251
12441252
"""
12451253
self.update_request_id()
1254+
# This direct connection doesn't use auth nor encryption so all secrets and tokens are invalid.
12461255
self.__login_secret = None
12471256
self.__device_secret = None
12481257
self.__session_token = None
12491258
self.__regain_token = None
12501259
self.__server_encryption_token = None
12511260
self.__device_encryption_token = None
1252-
self.__device_connection={"ip":ip,"port":port,"type":_type}
1253-
self.__dict_as_str = False
12541261
self.__devices = [{
12551262
'name': ip,
12561263
'id': 'direct',
12571264
'type': 'jd'
12581265
}]
1266+
self.__connection_type = "remoteapi"
12591267
self.__api_url="http://" + ip + ":" + str(port)
12601268
self.__content_type = "application/json; charset=utf-8"
1261-
self.__login_secret = None
1262-
if username and password:
1263-
self.__device_secret = self.__secret_create(username, password, "device")
1264-
else:
1265-
self.__device_secret = None
1266-
if not (timeout is None):
1267-
self.__timeout=timeout
1268-
self.__connected = True
1269+
self.__timeout=timeout
1270+
self.__connected = True # Set as already connected to use the request_api to ping the instance. Will set correct after that if the connection works.
12691271
response = self.request_api("/device/ping", "GET", [])['data']
12701272
self.__connected = response
12711273
self.update_request_id()
1272-
self.__session_token = None
1273-
self.__regain_token = None
12741274
return response
12751275

12761276
def reconnect(self):
@@ -1280,8 +1280,9 @@ def reconnect(self):
12801280
:returns: boolean -- True if successful, False if there was any error.
12811281
12821282
"""
1283-
if self.__connected and (not self.__session_token or not self.__regain_token):
1283+
if self.__connection_type == "remoteapi":
12841284
return True
1285+
12851286
response = self.request_api("/my/reconnect", "GET",
12861287
[("sessiontoken", self.__session_token),
12871288
("regaintoken", self.__regain_token)])
@@ -1298,7 +1299,7 @@ def disconnect(self):
12981299
:returns: boolean -- True if successful, False if there was any error.
12991300
13001301
"""
1301-
if self.__connected and not self.__session_token:
1302+
if self.__connection_type == "remoteapi":
13021303
response=True
13031304
else:
13041305
response = self.request_api("/my/disconnect", "GET",
@@ -1320,7 +1321,7 @@ def update_devices(self):
13201321
13211322
:returns: boolean -- True if successful, False if there was any error.
13221323
"""
1323-
if self.__connected and not self.__session_token:
1324+
if self.__connection_type == "remoteapi":
13241325
return
13251326
response = self.request_api("/my/listdevices", "GET",
13261327
[("sessiontoken", self.__session_token)])
@@ -1358,21 +1359,10 @@ def get_device(self, device_name=None, device_id=None):
13581359
for device in self.__devices:
13591360
if device["name"] == device_name:
13601361
return Jddevice(self, device)
1362+
elif len(self.__devices) > 0:
1363+
return Jddevice(self, self.__devices[0])
13611364
raise (MYJDDeviceNotFoundException("Device not found\n"))
13621365

1363-
def get_direct_Connection_infos(self,action_url):
1364-
if self.__connected and not self.__session_token:
1365-
return [self.__device_connection]
1366-
else:
1367-
response = self.request_api("/device/getDirectConnectionInfos",
1368-
"POST", None, action_url)
1369-
if response is not None \
1370-
and 'data' in response \
1371-
and 'infos' in response["data"] \
1372-
and len(response["data"]["infos"]) != 0:
1373-
return response["data"]["infos"]
1374-
else:
1375-
return None
13761366

13771367
def request_api(self,
13781368
path,
@@ -1403,8 +1393,8 @@ def request_api(self,
14031393
else:
14041394
query += ["&%s=%s" % (param[0], param[1])]
14051395
query += ["rid=" + str(self.__request_id)]
1406-
if not (self.__connected and not self.__session_token):
1407-
if self.__server_encryption_token is None:
1396+
if self.__connection_type == "myjd":
1397+
if self.__server_encryption_token is None: # Requests pre-auth.
14081398
query += [
14091399
"signature=" \
14101400
+ str(self.__signature_create(self.__login_secret,
@@ -1419,19 +1409,10 @@ def request_api(self,
14191409
query = query[0] + "&".join(query[1:])
14201410
encrypted_response = requests.get(api + query, timeout=self.__timeout)
14211411
else:
1422-
params_request = []
1423-
if params is not None:
1424-
for param in params:
1425-
if (isinstance(param, dict) and not self.__dict_as_str) or isinstance(param, str) or isinstance(param, list):
1426-
params_request += [param]
1427-
elif (isinstance(param, dict) and self.__dict_as_str) or isinstance(param, bool):
1428-
params_request += [json.dumps(param)]
1429-
else:
1430-
params_request += [str(param)]
14311412
params_request = {
14321413
"apiVer": self.__api_version,
14331414
"url": path,
1434-
"params": params_request,
1415+
"params": self.__adapt_params_for_request(params),
14351416
"rid": self.__request_id
14361417
}
14371418
data = json.dumps(params_request)
@@ -1488,3 +1469,26 @@ def request_api(self,
14881469
return None
14891470
self.update_request_id()
14901471
return jsondata
1472+
1473+
def get_connection_type(self):
1474+
return self.__connection_type
1475+
1476+
def __adapt_params_for_request(self, params):
1477+
if params is None:
1478+
return None
1479+
params_request = []
1480+
for param in params:
1481+
if isinstance(param, str):
1482+
params_request += [param]
1483+
elif isinstance(param, list):
1484+
params_request += [self.__adapt_params_for_request(param)]
1485+
elif isinstance(param, dict) and self.__connection_type == "remoteapi":
1486+
params_request += [param]
1487+
elif isinstance(param, dict):
1488+
params_request += [json.dumps(param)]
1489+
elif isinstance(param, bool) or isinstance(param, object):
1490+
params_request += [json.dumps(param)]
1491+
else:
1492+
params_request += [str(param)]
1493+
return params_request
1494+

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
long_description = f.read()
1313
setup(
1414
name="myjdapi",
15-
version="1.1.8",
15+
version="1.1.9",
1616
description="Library to use My.Jdownloader API in an easy way.",
1717
long_description=long_description,
1818
url="https://github.com/mmarquezs/My.Jdownloader-API-Python-Library/",

0 commit comments

Comments
 (0)