turing-usp commited on
Commit
3da5924
1 Parent(s): 379c3fa

Delete monkey_patch.py

Browse files
Files changed (1) hide show
  1. monkey_patch.py +0 -699
monkey_patch.py DELETED
@@ -1,699 +0,0 @@
1
- """
2
- This module contains all logic necessary to decipher the signature.
3
-
4
- YouTube's strategy to restrict downloading videos is to send a ciphered version
5
- of the signature to the client, along with the decryption algorithm obfuscated
6
- in JavaScript. For the clients to play the videos, JavaScript must take the
7
- ciphered version, cycle it through a series of "transform functions," and then
8
- signs the media URL with the output.
9
-
10
- This module is responsible for (1) finding and extracting those "transform
11
- functions" (2) maps them to Python equivalents and (3) taking the ciphered
12
- signature and decoding it.
13
-
14
- """
15
- import logging
16
- import re
17
- from itertools import chain
18
- from typing import Any, Callable, Dict, List, Optional, Tuple
19
-
20
- from pytube.exceptions import ExtractError, RegexMatchError
21
- from pytube.helpers import cache, regex_search
22
- from pytube.parser import find_object_from_startpoint, throttling_array_split
23
-
24
- logger = logging.getLogger(__name__)
25
-
26
- class CustomCipher:
27
- def __init__(self, js: str):
28
- self.transform_plan: List[str] = get_transform_plan(js)
29
- var_regex = re.compile(r"^[\w\$_]+\W")
30
- var_match = var_regex.search(self.transform_plan[0])
31
- if not var_match:
32
- raise RegexMatchError(
33
- caller="__init__", pattern=var_regex.pattern
34
- )
35
- var = var_match.group(0)[:-1]
36
- self.transform_map = get_transform_map(js, var)
37
- self.js_func_patterns = [
38
- r"\w+\.(\w+)\(\w,(\d+)\)",
39
- r"\w+\[(\"\w+\")\]\(\w,(\d+)\)"
40
- ]
41
-
42
- self.throttling_plan = get_throttling_plan(js)
43
- self.throttling_array = get_throttling_function_array(js)
44
-
45
- self.calculated_n = None
46
-
47
- def calculate_n(self, initial_n: list):
48
- """Converts n to the correct value to prevent throttling."""
49
- if self.calculated_n:
50
- return self.calculated_n
51
-
52
- # First, update all instances of 'b' with the list(initial_n)
53
- for i in range(len(self.throttling_array)):
54
- if self.throttling_array[i] == 'b':
55
- self.throttling_array[i] = initial_n
56
-
57
- for step in self.throttling_plan:
58
- curr_func = self.throttling_array[int(step[0])]
59
- if not callable(curr_func):
60
- logger.debug(f'{curr_func} is not callable.')
61
- logger.debug(f'Throttling array:\n{self.throttling_array}\n')
62
- raise ExtractError(f'{curr_func} is not callable.')
63
-
64
- first_arg = self.throttling_array[int(step[1])]
65
-
66
- if len(step) == 2:
67
- curr_func(first_arg)
68
- elif len(step) == 3:
69
- second_arg = self.throttling_array[int(step[2])]
70
- curr_func(first_arg, second_arg)
71
-
72
- self.calculated_n = ''.join(initial_n)
73
- return self.calculated_n
74
-
75
- def get_signature(self, ciphered_signature: str) -> str:
76
- """Decipher the signature.
77
-
78
- Taking the ciphered signature, applies the transform functions.
79
-
80
- :param str ciphered_signature:
81
- The ciphered signature sent in the ``player_config``.
82
- :rtype: str
83
- :returns:
84
- Decrypted signature required to download the media content.
85
- """
86
- signature = list(ciphered_signature)
87
-
88
- for js_func in self.transform_plan:
89
- name, argument = self.parse_function(js_func) # type: ignore
90
- signature = self.transform_map[name](signature, argument)
91
- logger.debug(
92
- "applied transform function\n"
93
- "output: %s\n"
94
- "js_function: %s\n"
95
- "argument: %d\n"
96
- "function: %s",
97
- "".join(signature),
98
- name,
99
- argument,
100
- self.transform_map[name],
101
- )
102
-
103
- return "".join(signature)
104
-
105
- @cache
106
- def parse_function(self, js_func: str) -> Tuple[str, int]:
107
- """Parse the Javascript transform function.
108
-
109
- Break a JavaScript transform function down into a two element ``tuple``
110
- containing the function name and some integer-based argument.
111
-
112
- :param str js_func:
113
- The JavaScript version of the transform function.
114
- :rtype: tuple
115
- :returns:
116
- two element tuple containing the function name and an argument.
117
-
118
- **Example**:
119
-
120
- parse_function('DE.AJ(a,15)')
121
- ('AJ', 15)
122
-
123
- """
124
- logger.debug("parsing transform function")
125
- for pattern in self.js_func_patterns:
126
- regex = re.compile(pattern)
127
- parse_match = regex.search(js_func)
128
- if parse_match:
129
- fn_name, fn_arg = parse_match.groups()
130
- return fn_name, int(fn_arg)
131
-
132
- raise RegexMatchError(
133
- caller="parse_function", pattern="js_func_patterns"
134
- )
135
-
136
-
137
- def get_initial_function_name(js: str) -> str:
138
- """Extract the name of the function responsible for computing the signature.
139
- :param str js:
140
- The contents of the base.js asset file.
141
- :rtype: str
142
- :returns:
143
- Function name from regex match
144
- """
145
-
146
- function_patterns = [
147
- r"\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
148
- r"\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*encodeURIComponent\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
149
- r'(?:\b|[^a-zA-Z0-9$])(?P<sig>[a-zA-Z0-9$]{2})\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)', # noqa: E501
150
- r'(?P<sig>[a-zA-Z0-9$]+)\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)', # noqa: E501
151
- r'(["\'])signature\1\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(',
152
- r"\.sig\|\|(?P<sig>[a-zA-Z0-9$]+)\(",
153
- r"yt\.akamaized\.net/\)\s*\|\|\s*.*?\s*[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?:encodeURIComponent\s*\()?\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
154
- r"\b[cs]\s*&&\s*[adf]\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
155
- r"\b[a-zA-Z0-9]+\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
156
- r"\bc\s*&&\s*a\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
157
- r"\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
158
- r"\bc\s*&&\s*[a-zA-Z0-9]+\.set\([^,]+\s*,\s*\([^)]*\)\s*\(\s*(?P<sig>[a-zA-Z0-9$]+)\(", # noqa: E501
159
- ]
160
- logger.debug("finding initial function name")
161
- for pattern in function_patterns:
162
- regex = re.compile(pattern)
163
- function_match = regex.search(js)
164
- if function_match:
165
- logger.debug("finished regex search, matched: %s", pattern)
166
- return function_match.group(1)
167
-
168
- raise RegexMatchError(
169
- caller="get_initial_function_name", pattern="multiple"
170
- )
171
-
172
-
173
- def get_transform_plan(js: str) -> List[str]:
174
- """Extract the "transform plan".
175
-
176
- The "transform plan" is the functions that the ciphered signature is
177
- cycled through to obtain the actual signature.
178
-
179
- :param str js:
180
- The contents of the base.js asset file.
181
-
182
- **Example**:
183
-
184
- ['DE.AJ(a,15)',
185
- 'DE.VR(a,3)',
186
- 'DE.AJ(a,51)',
187
- 'DE.VR(a,3)',
188
- 'DE.kT(a,51)',
189
- 'DE.kT(a,8)',
190
- 'DE.VR(a,3)',
191
- 'DE.kT(a,21)']
192
- """
193
- name = re.escape(get_initial_function_name(js))
194
- pattern = r"%s=function\(\w\){[a-z=\.\(\"\)]*;(.*);(?:.+)}" % name
195
- logger.debug("getting transform plan")
196
- return regex_search(pattern, js, group=1).split(";")
197
-
198
-
199
- def get_transform_object(js: str, var: str) -> List[str]:
200
- """Extract the "transform object".
201
-
202
- The "transform object" contains the function definitions referenced in the
203
- "transform plan". The ``var`` argument is the obfuscated variable name
204
- which contains these functions, for example, given the function call
205
- ``DE.AJ(a,15)`` returned by the transform plan, "DE" would be the var.
206
-
207
- :param str js:
208
- The contents of the base.js asset file.
209
- :param str var:
210
- The obfuscated variable name that stores an object with all functions
211
- that descrambles the signature.
212
-
213
- **Example**:
214
-
215
- >>> get_transform_object(js, 'DE')
216
- ['AJ:function(a){a.reverse()}',
217
- 'VR:function(a,b){a.splice(0,b)}',
218
- 'kT:function(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c}']
219
-
220
- """
221
- pattern = r"var %s={(.*?)};" % re.escape(var)
222
- logger.debug("getting transform object")
223
- regex = re.compile(pattern, flags=re.DOTALL)
224
- transform_match = regex.search(js)
225
- if not transform_match:
226
- raise RegexMatchError(caller="get_transform_object", pattern=pattern)
227
-
228
- return transform_match.group(1).replace("\n", " ").split(", ")
229
-
230
-
231
- def get_transform_map(js: str, var: str) -> Dict:
232
- """Build a transform function lookup.
233
-
234
- Build a lookup table of obfuscated JavaScript function names to the
235
- Python equivalents.
236
-
237
- :param str js:
238
- The contents of the base.js asset file.
239
- :param str var:
240
- The obfuscated variable name that stores an object with all functions
241
- that descrambles the signature.
242
-
243
- """
244
- transform_object = get_transform_object(js, var)
245
- mapper = {}
246
- for obj in transform_object:
247
- # AJ:function(a){a.reverse()} => AJ, function(a){a.reverse()}
248
- name, function = obj.split(":", 1)
249
- fn = map_functions(function)
250
- mapper[name] = fn
251
- return mapper
252
-
253
-
254
- def get_throttling_function_name(js: str) -> str:
255
- """Extract the name of the function that computes the throttling parameter.
256
-
257
- :param str js:
258
- The contents of the base.js asset file.
259
- :rtype: str
260
- :returns:
261
- The name of the function used to compute the throttling parameter.
262
- """
263
- function_patterns = [
264
- # https://github.com/ytdl-org/youtube-dl/issues/29326#issuecomment-865985377
265
- # https://github.com/yt-dlp/yt-dlp/commit/48416bc4a8f1d5ff07d5977659cb8ece7640dcd8
266
- # var Bpa = [iha];
267
- # ...
268
- # a.C && (b = a.get("n")) && (b = Bpa[0](b), a.set("n", b),
269
- # Bpa.length || iha("")) }};
270
- # In the above case, `iha` is the relevant function name
271
- r'a\.[a-zA-Z]\s*&&\s*\([a-z]\s*=\s*a\.get\("n"\)\)\s*&&\s*'
272
- r'\([a-z]\s*=\s*([a-zA-Z0-9$]+)(\[\d+\])?\([a-z]\)',
273
- ]
274
- logger.debug('Finding throttling function name')
275
- for pattern in function_patterns:
276
- regex = re.compile(pattern)
277
- function_match = regex.search(js)
278
- if function_match:
279
- logger.debug("finished regex search, matched: %s", pattern)
280
- if len(function_match.groups()) == 1:
281
- return function_match.group(1)
282
- idx = function_match.group(2)
283
- if idx:
284
- idx = idx.strip("[]")
285
- array = re.search(
286
- r'var {nfunc}\s*=\s*(\[.+?\]);'.format(
287
- nfunc=re.escape(function_match.group(1))),
288
- js
289
- )
290
- if array:
291
- array = array.group(1).strip("[]").split(",")
292
- array = [x.strip() for x in array]
293
- return array[int(idx)]
294
-
295
- raise RegexMatchError(
296
- caller="get_throttling_function_name", pattern="multiple"
297
- )
298
-
299
-
300
- def get_throttling_function_code(js: str) -> str:
301
- """Extract the raw code for the throttling function.
302
-
303
- :param str js:
304
- The contents of the base.js asset file.
305
- :rtype: str
306
- :returns:
307
- The name of the function used to compute the throttling parameter.
308
- """
309
- # Begin by extracting the correct function name
310
- name = re.escape(get_throttling_function_name(js))
311
-
312
- # Identify where the function is defined
313
- pattern_start = r"%s=function\(\w\)" % name
314
- regex = re.compile(pattern_start)
315
- match = regex.search(js)
316
-
317
- # Extract the code within curly braces for the function itself, and merge any split lines
318
- code_lines_list = find_object_from_startpoint(js, match.span()[1]).split('\n')
319
- joined_lines = "".join(code_lines_list)
320
-
321
- # Prepend function definition (e.g. `Dea=function(a)`)
322
- return match.group(0) + joined_lines
323
-
324
-
325
- def get_throttling_function_array(js: str) -> List[Any]:
326
- """Extract the "c" array.
327
-
328
- :param str js:
329
- The contents of the base.js asset file.
330
- :returns:
331
- The array of various integers, arrays, and functions.
332
- """
333
- raw_code = get_throttling_function_code(js)
334
-
335
- array_start = r",c=\["
336
- array_regex = re.compile(array_start)
337
- match = array_regex.search(raw_code)
338
-
339
- array_raw = find_object_from_startpoint(raw_code, match.span()[1] - 1)
340
- str_array = throttling_array_split(array_raw)
341
-
342
- converted_array = []
343
- for el in str_array:
344
- try:
345
- converted_array.append(int(el))
346
- continue
347
- except ValueError:
348
- # Not an integer value.
349
- pass
350
-
351
- if el == 'null':
352
- converted_array.append(None)
353
- continue
354
-
355
- if el.startswith('"') and el.endswith('"'):
356
- # Convert e.g. '"abcdef"' to string without quotation marks, 'abcdef'
357
- converted_array.append(el[1:-1])
358
- continue
359
-
360
- if el.startswith('function'):
361
- mapper = (
362
- (r"{for\(\w=\(\w%\w\.length\+\w\.length\)%\w\.length;\w--;\)\w\.unshift\(\w.pop\(\)\)}", throttling_unshift), # noqa:E501
363
- (r"{\w\.reverse\(\)}", throttling_reverse),
364
- (r"{\w\.push\(\w\)}", throttling_push),
365
- (r";var\s\w=\w\[0\];\w\[0\]=\w\[\w\];\w\[\w\]=\w}", throttling_swap),
366
- (r"case\s\d+", throttling_cipher_function),
367
- (r"\w\.splice\(0,1,\w\.splice\(\w,1,\w\[0\]\)\[0\]\)", throttling_nested_splice), # noqa:E501
368
- (r";\w\.splice\(\w,1\)}", js_splice),
369
- (r"\w\.splice\(-\w\)\.reverse\(\)\.forEach\(function\(\w\){\w\.unshift\(\w\)}\)", throttling_prepend), # noqa:E501
370
- (r"for\(var \w=\w\.length;\w;\)\w\.push\(\w\.splice\(--\w,1\)\[0\]\)}", throttling_reverse), # noqa:E501
371
- )
372
-
373
- found = False
374
- for pattern, fn in mapper:
375
- if re.search(pattern, el):
376
- converted_array.append(fn)
377
- found = True
378
- if found:
379
- continue
380
-
381
- converted_array.append(el)
382
-
383
- # Replace null elements with array itself
384
- for i in range(len(converted_array)):
385
- if converted_array[i] is None:
386
- converted_array[i] = converted_array
387
-
388
- return converted_array
389
-
390
-
391
- def get_throttling_plan(js: str):
392
- """Extract the "throttling plan".
393
-
394
- The "throttling plan" is a list of tuples used for calling functions
395
- in the c array. The first element of the tuple is the index of the
396
- function to call, and any remaining elements of the tuple are arguments
397
- to pass to that function.
398
-
399
- :param str js:
400
- The contents of the base.js asset file.
401
- :returns:
402
- The full function code for computing the throttlign parameter.
403
- """
404
- raw_code = get_throttling_function_code(js)
405
-
406
- transform_start = r"try{"
407
- plan_regex = re.compile(transform_start)
408
- match = plan_regex.search(raw_code)
409
-
410
- transform_plan_raw = find_object_from_startpoint(raw_code, match.span()[1] - 1)
411
-
412
- # Steps are either c[x](c[y]) or c[x](c[y],c[z])
413
- step_start = r"c\[(\d+)\]\(c\[(\d+)\](,c(\[(\d+)\]))?\)"
414
- step_regex = re.compile(step_start)
415
- matches = step_regex.findall(transform_plan_raw)
416
- transform_steps = []
417
- for match in matches:
418
- if match[4] != '':
419
- transform_steps.append((match[0],match[1],match[4]))
420
- else:
421
- transform_steps.append((match[0],match[1]))
422
-
423
- return transform_steps
424
-
425
-
426
- def reverse(arr: List, _: Optional[Any]):
427
- """Reverse elements in a list.
428
-
429
- This function is equivalent to:
430
-
431
- .. code-block:: javascript
432
-
433
- function(a, b) { a.reverse() }
434
-
435
- This method takes an unused ``b`` variable as their transform functions
436
- universally sent two arguments.
437
-
438
- **Example**:
439
-
440
- >>> reverse([1, 2, 3, 4])
441
- [4, 3, 2, 1]
442
- """
443
- return arr[::-1]
444
-
445
-
446
- def splice(arr: List, b: int):
447
- """Add/remove items to/from a list.
448
-
449
- This function is equivalent to:
450
-
451
- .. code-block:: javascript
452
-
453
- function(a, b) { a.splice(0, b) }
454
-
455
- **Example**:
456
-
457
- >>> splice([1, 2, 3, 4], 2)
458
- [1, 2]
459
- """
460
- return arr[b:]
461
-
462
-
463
- def swap(arr: List, b: int):
464
- """Swap positions at b modulus the list length.
465
-
466
- This function is equivalent to:
467
-
468
- .. code-block:: javascript
469
-
470
- function(a, b) { var c=a[0];a[0]=a[b%a.length];a[b]=c }
471
-
472
- **Example**:
473
-
474
- >>> swap([1, 2, 3, 4], 2)
475
- [3, 2, 1, 4]
476
- """
477
- r = b % len(arr)
478
- return list(chain([arr[r]], arr[1:r], [arr[0]], arr[r + 1 :]))
479
-
480
-
481
- def throttling_reverse(arr: list):
482
- """Reverses the input list.
483
-
484
- Needs to do an in-place reversal so that the passed list gets changed.
485
- To accomplish this, we create a reversed copy, and then change each
486
- indvidual element.
487
- """
488
- reverse_copy = arr.copy()[::-1]
489
- for i in range(len(reverse_copy)):
490
- arr[i] = reverse_copy[i]
491
-
492
-
493
- def throttling_push(d: list, e: Any):
494
- """Pushes an element onto a list."""
495
- d.append(e)
496
-
497
-
498
- def throttling_mod_func(d: list, e: int):
499
- """Perform the modular function from the throttling array functions.
500
-
501
- In the javascript, the modular operation is as follows:
502
- e = (e % d.length + d.length) % d.length
503
-
504
- We simply translate this to python here.
505
- """
506
- return (e % len(d) + len(d)) % len(d)
507
-
508
-
509
- def throttling_unshift(d: list, e: int):
510
- """Rotates the elements of the list to the right.
511
-
512
- In the javascript, the operation is as follows:
513
- for(e=(e%d.length+d.length)%d.length;e--;)d.unshift(d.pop())
514
- """
515
- e = throttling_mod_func(d, e)
516
- new_arr = d[-e:] + d[:-e]
517
- d.clear()
518
- for el in new_arr:
519
- d.append(el)
520
-
521
-
522
- def throttling_cipher_function(d: list, e: str):
523
- """This ciphers d with e to generate a new list.
524
-
525
- In the javascript, the operation is as follows:
526
- var h = [A-Za-z0-9-_], f = 96; // simplified from switch-case loop
527
- d.forEach(
528
- function(l,m,n){
529
- this.push(
530
- n[m]=h[
531
- (h.indexOf(l)-h.indexOf(this[m])+m-32+f--)%h.length
532
- ]
533
- )
534
- },
535
- e.split("")
536
- )
537
- """
538
- h = list('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_')
539
- f = 96
540
- # by naming it "this" we can more closely reflect the js
541
- this = list(e)
542
-
543
- # This is so we don't run into weirdness with enumerate while
544
- # we change the input list
545
- copied_list = d.copy()
546
-
547
- for m, l in enumerate(copied_list):
548
- bracket_val = (h.index(l) - h.index(this[m]) + m - 32 + f) % len(h)
549
- this.append(
550
- h[bracket_val]
551
- )
552
- d[m] = h[bracket_val]
553
- f -= 1
554
-
555
-
556
- def throttling_nested_splice(d: list, e: int):
557
- """Nested splice function in throttling js.
558
-
559
- In the javascript, the operation is as follows:
560
- function(d,e){
561
- e=(e%d.length+d.length)%d.length;
562
- d.splice(
563
- 0,
564
- 1,
565
- d.splice(
566
- e,
567
- 1,
568
- d[0]
569
- )[0]
570
- )
571
- }
572
-
573
- While testing, all this seemed to do is swap element 0 and e,
574
- but the actual process is preserved in case there was an edge
575
- case that was not considered.
576
- """
577
- e = throttling_mod_func(d, e)
578
- inner_splice = js_splice(
579
- d,
580
- e,
581
- 1,
582
- d[0]
583
- )
584
- js_splice(
585
- d,
586
- 0,
587
- 1,
588
- inner_splice[0]
589
- )
590
-
591
-
592
- def throttling_prepend(d: list, e: int):
593
- """
594
-
595
- In the javascript, the operation is as follows:
596
- function(d,e){
597
- e=(e%d.length+d.length)%d.length;
598
- d.splice(-e).reverse().forEach(
599
- function(f){
600
- d.unshift(f)
601
- }
602
- )
603
- }
604
-
605
- Effectively, this moves the last e elements of d to the beginning.
606
- """
607
- start_len = len(d)
608
- # First, calculate e
609
- e = throttling_mod_func(d, e)
610
-
611
- # Then do the prepending
612
- new_arr = d[-e:] + d[:-e]
613
-
614
- # And update the input list
615
- d.clear()
616
- for el in new_arr:
617
- d.append(el)
618
-
619
- end_len = len(d)
620
- assert start_len == end_len
621
-
622
-
623
- def throttling_swap(d: list, e: int):
624
- """Swap positions of the 0'th and e'th elements in-place."""
625
- e = throttling_mod_func(d, e)
626
- f = d[0]
627
- d[0] = d[e]
628
- d[e] = f
629
-
630
-
631
- def js_splice(arr: list, start: int, delete_count=None, *items):
632
- """Implementation of javascript's splice function.
633
-
634
- :param list arr:
635
- Array to splice
636
- :param int start:
637
- Index at which to start changing the array
638
- :param int delete_count:
639
- Number of elements to delete from the array
640
- :param *items:
641
- Items to add to the array
642
-
643
- Reference: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice # noqa:E501
644
- """
645
- # Special conditions for start value
646
- try:
647
- if start > len(arr):
648
- start = len(arr)
649
- # If start is negative, count backwards from end
650
- if start < 0:
651
- start = len(arr) - start
652
- except TypeError:
653
- # Non-integer start values are treated as 0 in js
654
- start = 0
655
-
656
- # Special condition when delete_count is greater than remaining elements
657
- if not delete_count or delete_count >= len(arr) - start:
658
- delete_count = len(arr) - start # noqa: N806
659
-
660
- deleted_elements = arr[start:start + delete_count]
661
-
662
- # Splice appropriately.
663
- new_arr = arr[:start] + list(items) + arr[start + delete_count:]
664
-
665
- # Replace contents of input array
666
- arr.clear()
667
- for el in new_arr:
668
- arr.append(el)
669
-
670
- return deleted_elements
671
-
672
-
673
- def map_functions(js_func: str) -> Callable:
674
- """For a given JavaScript transform function, return the Python equivalent.
675
-
676
- :param str js_func:
677
- The JavaScript version of the transform function.
678
- """
679
- mapper = (
680
- # function(a){a.reverse()}
681
- (r"{\w\.reverse\(\)}", reverse),
682
- # function(a,b){a.splice(0,b)}
683
- (r"{\w\.splice\(0,\w\)}", splice),
684
- # function(a,b){var c=a[0];a[0]=a[b%a.length];a[b]=c}
685
- (r"{var\s\w=\w\[0\];\w\[0\]=\w\[\w\%\w.length\];\w\[\w\]=\w}", swap),
686
- # function(a,b){var c=a[0];a[0]=a[b%a.length];a[b%a.length]=c}
687
- (
688
- r"{var\s\w=\w\[0\];\w\[0\]=\w\[\w\%\w.length\];\w\[\w\%\w.length\]=\w}",
689
- swap,
690
- ),
691
- )
692
-
693
- for pattern, fn in mapper:
694
- if re.search(pattern, js_func):
695
- return fn
696
- raise RegexMatchError(caller="map_functions", pattern="multiple")
697
-
698
- from pytube import cipher
699
- cipher.Cipher.__init__ = CustomCipher.__init__