Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
anasty17
GitHub Repository: anasty17/mirror-leech-telegram-bot
Path: blob/master/myjd/myjdapi.py
1631 views
1
from json import JSONDecodeError
2
from httpx import AsyncClient, AsyncHTTPTransport, RequestError, Timeout
3
4
from .exception import (
5
MYJDApiException,
6
MYJDConnectionException,
7
MYJDDecodeException,
8
)
9
10
11
class System:
12
def __init__(self, device):
13
self.device = device
14
self.url = "/system"
15
16
async def exit_jd(self):
17
return await self.device.action(f"{self.url}/exitJD")
18
19
async def restart_jd(self):
20
return await self.device.action(f"{self.url}/restartJD")
21
22
async def hibernate_os(self):
23
return await self.device.action(f"{self.url}/hibernateOS")
24
25
async def shutdown_os(self, force):
26
params = force
27
return await self.device.action(f"{self.url}/shutdownOS", params)
28
29
async def standby_os(self):
30
return await self.device.action(f"{self.url}/standbyOS")
31
32
async def get_storage_info(self):
33
return await self.device.action(f"{self.url}/getStorageInfos?path")
34
35
36
class Jd:
37
def __init__(self, device):
38
self.device = device
39
self.url = "/jd"
40
41
async def get_core_revision(self):
42
return await self.device.action(f"{self.url}/getCoreRevision")
43
44
async def version(self):
45
return await self.device.action(f"{self.url}/version")
46
47
48
class Config:
49
50
def __init__(self, device):
51
self.device = device
52
self.url = "/config"
53
54
async def list(self, params=None):
55
"""
56
:return: List<AdvancedConfigAPIEntry>
57
"""
58
if params is None:
59
return await self.device.action(f"{self.url}/list", params)
60
else:
61
return await self.device.action(f"{self.url}/list")
62
63
async def listEnum(self, type):
64
"""
65
:return: List<EnumOption>
66
"""
67
return await self.device.action(f"{self.url}/listEnum", params=[type])
68
69
async def get(self, interface_name, storage, key):
70
"""
71
:param interfaceName: a valid interface name from List<AdvancedConfigAPIEntry>
72
:type: str:
73
:param storage: 'null' to use default or 'cfg/' + interfaceName
74
:type: str:
75
:param key: a valid key from from List<AdvancedConfigAPIEntry>
76
:type: str:
77
"""
78
params = [interface_name, storage, key]
79
return await self.device.action(f"{self.url}/get", params)
80
81
async def getDefault(self, interfaceName, storage, key):
82
"""
83
:param interfaceName: a valid interface name from List<AdvancedConfigAPIEntry>
84
:type: str:
85
:param storage: 'null' to use default or 'cfg/' + interfaceName
86
:type: str:
87
:param key: a valid key from from List<AdvancedConfigAPIEntry>
88
:type: str:
89
"""
90
params = [interfaceName, storage, key]
91
return await self.device.action(f"{self.url}/getDefault", params)
92
93
async def query(self, params=None):
94
"""
95
:param params: A dictionary with options. The default dictionary is
96
configured so it returns you all config API entries with all details, but you
97
can put your own with your options. All the options available are this
98
ones:
99
{
100
"configInterface" : "",
101
"defaultValues" : True,
102
"description" : True,
103
"enumInfo" : True,
104
"includeExtensions": True,
105
"pattern" : "",
106
"values" : ""
107
}
108
:type: Dictionary
109
:rtype: List of dictionaries of this style, with more or less detail based on your options.
110
"""
111
if params is None:
112
params = [
113
{
114
"configInterface": "",
115
"defaultValues": True,
116
"description": True,
117
"enumInfo": True,
118
"includeExtensions": True,
119
"pattern": "",
120
"values": True,
121
}
122
]
123
return await self.device.action(f"{self.url}/query", params)
124
125
async def reset(self, interfaceName, storage, key):
126
"""
127
:param interfaceName: a valid interface name from List<AdvancedConfigAPIEntry>
128
:type: str:
129
:param storage: 'null' to use default or 'cfg/' + interfaceName
130
:type: str:
131
:param key: a valid key from from List<AdvancedConfigAPIEntry>
132
:type: str:
133
"""
134
params = [interfaceName, storage, key]
135
return await self.device.action(f"{self.url}/reset", params)
136
137
async def set(self, interface_name, storage, key, value):
138
"""
139
:param interfaceName: a valid interface name from List<AdvancedConfigAPIEntry>
140
:type: str:
141
:param storage: 'null' to use default or 'cfg/' + interfaceName
142
:type: str:
143
:param key: a valid key from from List<AdvancedConfigAPIEntry>
144
:type: str:
145
:param value: a valid value for the given key (see type value from List<AdvancedConfigAPIEntry>)
146
:type: Object:
147
"""
148
params = [interface_name, storage, key, value]
149
return await self.device.action(f"{self.url}/set", params)
150
151
152
class DownloadController:
153
154
def __init__(self, device):
155
self.device = device
156
self.url = "/downloadcontroller"
157
158
async def start_downloads(self):
159
return await self.device.action(f"{self.url}/start")
160
161
async def stop_downloads(self):
162
return await self.device.action(f"{self.url}/stop")
163
164
async def pause_downloads(self, value):
165
params = [value]
166
return await self.device.action(f"{self.url}/pause", params)
167
168
async def get_speed_in_bytes(self):
169
return await self.device.action(f"{self.url}/getSpeedInBps")
170
171
async def force_download(self, link_ids, package_ids):
172
params = [link_ids, package_ids]
173
return await self.device.action(f"{self.url}/forceDownload", params)
174
175
async def get_current_state(self):
176
return await self.device.action(f"{self.url}/getCurrentState")
177
178
179
class Extension:
180
def __init__(self, device):
181
self.device = device
182
self.url = "/extensions"
183
184
async def list(self, params=None):
185
"""
186
:param params: A dictionary with options. The default dictionary is
187
configured so it returns you all available extensions, but you
188
can put your own with your options. All the options available are this
189
ones:
190
{
191
"configInterface" : True,
192
"description" : True,
193
"enabled" : True,
194
"iconKey" : True,
195
"name" : True,
196
"pattern" : "",
197
"installed" : True
198
}
199
:type: Dictionary
200
:rtype: List of dictionaries of this style, with more or less detail based on your options.
201
"""
202
if params is None:
203
params = [
204
{
205
"configInterface": True,
206
"description": True,
207
"enabled": True,
208
"iconKey": True,
209
"name": True,
210
"pattern": "",
211
"installed": True,
212
}
213
]
214
return await self.device.action(f"{self.url}/list", params=params)
215
216
async def install(self, id):
217
return await self.device.action(f"{self.url}/install", params=[id])
218
219
async def isInstalled(self, id):
220
return await self.device.action(f"{self.url}/isInstalled", params=[id])
221
222
async def isEnabled(self, id):
223
return await self.device.action(f"{self.url}/isEnabled", params=[id])
224
225
async def setEnabled(self, id, enabled):
226
return await self.device.action(f"{self.url}/setEnabled", params=[id, enabled])
227
228
229
class Linkgrabber:
230
231
def __init__(self, device):
232
self.device = device
233
self.url = "/linkgrabberv2"
234
235
async def clear_list(self):
236
return await self.device.action(f"{self.url}/clearList")
237
238
async def move_to_downloadlist(self, link_ids=None, package_ids=None):
239
"""
240
Moves packages and/or links to download list.
241
242
:param package_ids: Package UUID's.
243
:type: list of strings.
244
:param link_ids: Link UUID's.
245
"""
246
if link_ids is None:
247
link_ids = []
248
if package_ids is None:
249
package_ids = []
250
params = [link_ids, package_ids]
251
return await self.device.action(f"{self.url}/moveToDownloadlist", params)
252
253
async def query_links(self, params=None):
254
"""
255
256
Get the links in the linkcollector/linkgrabber
257
258
:param params: A dictionary with options. The default dictionary is
259
configured so it returns you all the downloads with all details, but you
260
can put your own with your options. All the options available are this
261
ones:
262
{
263
"bytesTotal" : false,
264
"comment" : false,
265
"status" : false,
266
"enabled" : false,
267
"maxResults" : -1,
268
"startAt" : 0,
269
"packageUUIDs" : null,
270
"hosts" : false,
271
"url" : false,
272
"availability" : false,
273
"variantIcon" : false,
274
"variantName" : false,
275
"variantID" : false,
276
"variants" : false,
277
"priority" : false
278
}
279
:type: Dictionary
280
:rtype: List of dictionaries of this style, with more or less detail based on your options.
281
282
[ { 'availability': 'ONLINE',
283
'bytesTotal': 68548274,
284
'enabled': True,
285
'name': 'The Rick And Morty Theory - The Original Morty_ - '
286
'Cartoon Conspiracy (Ep. 74) @ChannelFred (192kbit).m4a',
287
'packageUUID': 1450430888524,
288
'url': 'youtubev2://DEMUX_M4A_192_720P_V4/d1NZf1w2BxQ/',
289
'uuid': 1450430889576,
290
'variant': { 'id': 'DEMUX_M4A_192_720P_V4',
291
'name': '192kbit/s M4A-Audio'},
292
'variants': True
293
}, ... ]
294
"""
295
if params is None:
296
params = [
297
{
298
"bytesTotal": True,
299
"comment": True,
300
"status": True,
301
"enabled": True,
302
"maxResults": -1,
303
"startAt": 0,
304
"hosts": True,
305
"url": True,
306
"availability": True,
307
"variantIcon": True,
308
"variantName": True,
309
"variantID": True,
310
"variants": True,
311
"priority": True,
312
}
313
]
314
return await self.device.action(f"{self.url}/queryLinks", params)
315
316
async def cleanup(
317
self, action, mode, selection_type, link_ids=None, package_ids=None
318
):
319
"""
320
Clean packages and/or links of the linkgrabber list.
321
Requires at least a package_ids or link_ids list, or both.
322
323
:param package_ids: Package UUID's.
324
:type: list of strings.
325
:param link_ids: link UUID's.
326
:type: list of strings
327
:param action: Action to be done. Actions: DELETE_ALL, DELETE_DISABLED, DELETE_FAILED, DELETE_FINISHED, DELETE_OFFLINE, DELETE_DUPE, DELETE_MODE
328
:type: str:
329
:param mode: Mode to use. Modes: REMOVE_LINKS_AND_DELETE_FILES, REMOVE_LINKS_AND_RECYCLE_FILES, REMOVE_LINKS_ONLY
330
:type: str:
331
:param selection_type: Type of selection to use. Types: SELECTED, UNSELECTED, ALL, NONE
332
:type: str:
333
"""
334
if link_ids is None:
335
link_ids = []
336
if package_ids is None:
337
package_ids = []
338
params = [link_ids, package_ids]
339
params += [action, mode, selection_type]
340
return await self.device.action(f"{self.url}/cleanup", params)
341
342
async def add_container(self, type_, content):
343
"""
344
Adds a container to Linkgrabber.
345
346
:param type_: Type of container.
347
:type: string.
348
:param content: The container.
349
:type: string.
350
351
"""
352
params = [type_, content]
353
return await self.device.action(f"{self.url}/addContainer", params)
354
355
async def get_download_urls(self, link_ids, package_ids, url_display_type):
356
"""
357
Gets download urls from Linkgrabber.
358
359
:param package_ids: Package UUID's.
360
:type: List of strings.
361
:param link_ids: link UUID's.
362
:type: List of strings
363
:param url_display_type: No clue. Not documented
364
:type: Dictionary
365
"""
366
params = [package_ids, link_ids, url_display_type]
367
return await self.device.action(f"{self.url}/getDownloadUrls", params)
368
369
async def set_priority(self, priority, link_ids, package_ids):
370
"""
371
Sets the priority of links or packages.
372
373
:param package_ids: Package UUID's.
374
:type: list of strings.
375
:param link_ids: link UUID's.
376
:type: list of strings
377
:param priority: Priority to set. Priorities: HIGHEST, HIGHER, HIGH, DEFAULT, LOWER;
378
:type: str:
379
"""
380
params = [priority, link_ids, package_ids]
381
return await self.device.action(f"{self.url}/setPriority", params)
382
383
async def set_enabled(self, enable, link_ids, package_ids):
384
"""
385
Enable or disable packages.
386
387
:param enable: Enable or disable package.
388
:type: boolean
389
:param link_ids: Links UUID.
390
:type: list of strings
391
:param package_ids: Packages UUID.
392
:type: list of strings.
393
"""
394
params = [enable, link_ids, package_ids]
395
return await self.device.action(f"{self.url}/setEnabled", params)
396
397
async def get_variants(self, params):
398
"""
399
Gets the variants of a url/download (not package), for example a youtube
400
link gives you a package with three downloads, the audio, the video and
401
a picture, and each of those downloads have different variants (audio
402
quality, video quality, and picture quality).
403
404
:param params: List with the UUID of the download you want the variants. Ex: [232434]
405
:type: List
406
:rtype: Variants in a list with dictionaries like this one: [{'id':
407
'M4A_256', 'name': '256kbit/s M4A-Audio'}, {'id': 'AAC_256', 'name':
408
'256kbit/s AAC-Audio'},.......]
409
"""
410
return await self.device.action(f"{self.url}/getVariants", params)
411
412
async def add_links(self, params=None):
413
"""
414
Add links to the linkcollector
415
416
{
417
"autostart" : false,
418
"links" : null,
419
"packageName" : null,
420
"extractPassword" : null,
421
"priority" : "DEFAULT",
422
"downloadPassword" : null,
423
"destinationFolder" : null
424
}
425
"""
426
if params is None:
427
params = [
428
{
429
"autostart": False,
430
"links": None,
431
"packageName": None,
432
"extractPassword": None,
433
"priority": "DEFAULT",
434
"downloadPassword": None,
435
"destinationFolder": None,
436
"overwritePackagizerRules": False,
437
}
438
]
439
return await self.device.action(f"{self.url}/addLinks", params)
440
441
async def is_collecting(self):
442
"""
443
Boolean status query about the collecting process
444
"""
445
return await self.device.action(f"{self.url}/isCollecting")
446
447
async def set_download_directory(self, dir: str, package_ids: list):
448
params = [dir, package_ids]
449
return await self.device.action(f"{self.url}/setDownloadDirectory", params)
450
451
async def move_to_new_package(
452
self, name: str, path: str, link_ids: list = None, package_ids: list = None
453
):
454
# Requires at least a link_ids or package_ids list, or both.
455
if link_ids is None:
456
link_ids = []
457
if package_ids is None:
458
package_ids = []
459
params = [link_ids, package_ids, name, path]
460
return await self.device.action(f"{self.url}/movetoNewPackage", params)
461
462
async def remove_links(self, link_ids=None, package_ids=None):
463
"""
464
Remove packages and/or links of the linkgrabber list.
465
Requires at least a link_ids or package_ids list, or both.
466
467
:param link_ids: link UUID's.
468
:type: list of strings
469
:param package_ids: Package UUID's.
470
:type: list of strings.
471
"""
472
if link_ids is None:
473
link_ids = []
474
if package_ids is None:
475
package_ids = []
476
params = [link_ids, package_ids]
477
return await self.device.action(f"{self.url}/removeLinks", params)
478
479
async def rename_link(self, link_id, new_name):
480
"""
481
Renames files related with link_id
482
"""
483
params = [link_id, new_name]
484
return await self.device.action(f"{self.url}/renameLink", params)
485
486
async def get_package_count(self):
487
return await self.device.action(f"{self.url}/getPackageCount")
488
489
async def rename_package(self, package_id, new_name):
490
"""
491
Rename package name with package_id
492
"""
493
params = [package_id, new_name]
494
return await self.device.action(f"{self.url}/renamePackage", params)
495
496
async def query_packages(self, params=None):
497
if params is None:
498
params = [
499
{
500
"availableOfflineCount": True,
501
"availableOnlineCount": True,
502
"availableTempUnknownCount": True,
503
"availableUnknownCount": True,
504
"bytesTotal": True,
505
"childCount": True,
506
"comment": True,
507
"enabled": True,
508
"hosts": True,
509
"maxResults": -1,
510
"packageUUIDs": [],
511
"priority": True,
512
"saveTo": True,
513
"startAt": 0,
514
"status": True,
515
}
516
]
517
return await self.device.action(f"{self.url}/queryPackages", params)
518
519
520
class Downloads:
521
522
def __init__(self, device):
523
self.device = device
524
self.url = "/downloadsV2"
525
526
async def query_links(self, params=None):
527
"""
528
Get the links in the download list
529
"""
530
if params is None:
531
params = [
532
{
533
"addedDate": True,
534
"bytesLoaded": True,
535
"bytesTotal": True,
536
"comment": True,
537
"enabled": True,
538
"eta": True,
539
"extractionStatus": True,
540
"finished": True,
541
"finishedDate": True,
542
"host": True,
543
"jobUUIDs": [],
544
"maxResults": -1,
545
"packageUUIDs": [],
546
"password": True,
547
"priority": True,
548
"running": True,
549
"skipped": True,
550
"speed": True,
551
"startAt": 0,
552
"status": True,
553
"url": True,
554
}
555
]
556
return await self.device.action(f"{self.url}/queryLinks", params)
557
558
async def query_packages(self, params=None):
559
if params is None:
560
params = [
561
{
562
"bytesLoaded": True,
563
"bytesTotal": True,
564
"childCount": True,
565
"comment": True,
566
"enabled": True,
567
"eta": True,
568
"finished": True,
569
"hosts": True,
570
"maxResults": -1,
571
"packageUUIDs": [],
572
"priority": True,
573
"running": True,
574
"saveTo": True,
575
"speed": True,
576
"startAt": 0,
577
"status": True,
578
}
579
]
580
return await self.device.action(f"{self.url}/queryPackages", params)
581
582
async def cleanup(
583
self, action, mode, selection_type, link_ids=None, package_ids=None
584
):
585
"""
586
Clean packages and/or links of the linkgrabber list.
587
Requires at least a package_ids or link_ids list, or both.
588
589
:param package_ids: Package UUID's.
590
:type: list of strings.
591
:param link_ids: link UUID's.
592
:type: list of strings
593
:param action: Action to be done. Actions: DELETE_ALL, DELETE_DISABLED, DELETE_FAILED, DELETE_FINISHED, DELETE_OFFLINE, DELETE_DUPE, DELETE_MODE
594
:type: str:
595
:param mode: Mode to use. Modes: REMOVE_LINKS_AND_DELETE_FILES, REMOVE_LINKS_AND_RECYCLE_FILES, REMOVE_LINKS_ONLY
596
:type: str:
597
:param selection_type: Type of selection to use. Types: SELECTED, UNSELECTED, ALL, NONE
598
:type: str:
599
"""
600
if link_ids is None:
601
link_ids = []
602
if package_ids is None:
603
package_ids = []
604
params = [link_ids, package_ids]
605
params += [action, mode, selection_type]
606
return await self.device.action(f"{self.url}/cleanup", params)
607
608
async def set_enabled(self, enable, link_ids, package_ids):
609
"""
610
Enable or disable packages.
611
612
:param enable: Enable or disable package.
613
:type: boolean
614
:param link_ids: Links UUID.
615
:type: list of strings
616
:param package_ids: Packages UUID.
617
:type: list of strings.
618
"""
619
params = [enable, link_ids, package_ids]
620
return await self.device.action(f"{self.url}/setEnabled", params)
621
622
async def force_download(self, link_ids=None, package_ids=None):
623
if link_ids is None:
624
link_ids = []
625
if package_ids is None:
626
package_ids = []
627
params = [link_ids, package_ids]
628
return await self.device.action(f"{self.url}/forceDownload", params)
629
630
async def set_dl_location(self, directory, package_ids=None):
631
if package_ids is None:
632
package_ids = []
633
params = [directory, package_ids]
634
return await self.device.action(f"{self.url}/setDownloadDirectory", params)
635
636
async def remove_links(self, link_ids=None, package_ids=None):
637
"""
638
Remove packages and/or links of the downloads list.
639
NOTE: For more specific removal, like deleting the files etc, use the /cleanup api.
640
Requires at least a link_ids or package_ids list, or both.
641
642
:param link_ids: link UUID's.
643
:type: list of strings
644
:param package_ids: Package UUID's.
645
:type: list of strings.
646
"""
647
if link_ids is None:
648
link_ids = []
649
if package_ids is None:
650
package_ids = []
651
params = [link_ids, package_ids]
652
return await self.device.action(f"{self.url}/removeLinks", params)
653
654
async def reset_links(self, link_ids, package_ids):
655
params = [link_ids, package_ids]
656
return await self.device.action(f"{self.url}/resetLinks", params)
657
658
async def move_to_new_package(
659
self, link_ids, package_ids, new_pkg_name, download_path
660
):
661
params = [link_ids, package_ids, new_pkg_name, download_path]
662
return await self.device.action(f"{self.url}/movetoNewPackage", params)
663
664
async def rename_link(self, link_id: list, new_name: str):
665
params = [link_id, new_name]
666
return await self.device.action(f"{self.url}/renameLink", params)
667
668
669
class Captcha:
670
671
def __init__(self, device):
672
self.device = device
673
self.url = "/captcha"
674
675
async def list(self):
676
return await self.device.action(f"{self.url}/list", [])
677
678
async def get(self, captcha_id):
679
return await self.device.action(f"{self.url}/get", (captcha_id,))
680
681
async def solve(self, captcha_id, solution):
682
return await self.device.action(f"{self.url}/solve", (captcha_id, solution))
683
684
685
class Jddevice:
686
687
def __init__(self, jd):
688
"""This functions initializes the device instance.
689
It uses the provided dictionary to create the device.
690
691
:param device_dict: Device dictionary
692
"""
693
self.myjd = jd
694
self.config = Config(self)
695
self.linkgrabber = Linkgrabber(self)
696
self.captcha = Captcha(self)
697
self.downloads = Downloads(self)
698
self.downloadcontroller = DownloadController(self)
699
self.extensions = Extension(self)
700
self.jd = Jd(self)
701
self.system = System(self)
702
703
async def ping(self):
704
return await self.action("/device/ping")
705
706
async def action(self, path, params=()):
707
response = await self.myjd.request_api(path, params)
708
if response is None:
709
raise (MYJDConnectionException("No connection established\n"))
710
return response["data"]
711
712
713
class MyJdApi:
714
715
def __init__(self):
716
self.__api_url = "http://127.0.0.1:3128"
717
self._http_session = None
718
self.device = Jddevice(self)
719
720
def _session(self):
721
if self._http_session is not None:
722
return self._http_session
723
724
transport = AsyncHTTPTransport(retries=10, verify=False)
725
726
self._http_session = AsyncClient(
727
base_url=self.__api_url,
728
transport=transport,
729
headers={"Content-Type": "application/json; charset=utf-8"},
730
timeout=Timeout(connect=60, read=60, write=60, pool=None),
731
follow_redirects=True,
732
verify=False,
733
)
734
735
return self._http_session
736
737
async def close(self):
738
if self._http_session is not None:
739
await self._http_session.aclose()
740
self._http_session = None
741
742
async def request_api(self, path, params=None):
743
session = self._session()
744
params_request = params if params is not None else []
745
params_request = {
746
"params": params_request,
747
}
748
try:
749
res = await session.post(
750
path,
751
json=params_request,
752
)
753
except RequestError:
754
return None
755
try:
756
response = res.json()
757
except JSONDecodeError as exc:
758
raise MYJDDecodeException(
759
"Failed to decode response: {}", response
760
) from exc
761
if res.status_code != 200:
762
msg = (
763
"\n\tSOURCE: "
764
+ response.get("src", "UNKNOWN_SOURCE")
765
+ "\n\tTYPE: "
766
+ response.get("type", "UNKNOWN_TYPE")
767
+ "\n------\nREQUEST_URL: "
768
+ self.__api_url
769
+ path
770
)
771
msg += "\n"
772
if params_request is not None:
773
msg += "DATA:\n" + params_request
774
raise (
775
MYJDApiException.get_exception(
776
response.get("src", "UNKNOWN_SOURCE"),
777
response.get("type", "UNKNOWN_TYPE"),
778
msg,
779
)
780
)
781
return response
782
783