Repository: yt-dlp/yt-dlp
Pull request #13238 (2025-05-20 23:25:17)

[jsinterp] Fix increment/decrement evaluation
<!--
    **IMPORTANT**: PRs without the template will be CLOSED
    
    Due to the high volume of pull requests, it may be a while before your PR is reviewed.
    Please try to keep your pull request focused on a single bugfix or new feature.
    Pull requests with a vast scope and/or very large diff will take much longer to review.
    It is recommended for new contributors to stick to smaller pull requests, so you can receive much more immediate feedback as you familiarize yourself with the codebase.

    PLEASE AVOID FORCE-PUSHING after opening a PR, as it makes reviewing more difficult.
-->

### Description of your *pull request* and other information

~~This is very cringe and I hate it, but for now as a hotfix this has to suffice.~~
Hopefully we can deprecate jsinterp soon...


<details open><summary>Template</summary> <!-- OPEN is intentional -->

<!--
    # PLEASE FOLLOW THE GUIDE BELOW

    - You will be asked some questions, please read them **carefully** and answer honestly
    - Put an `x` into all the boxes `[ ]` relevant to your *pull request* (like [x])
    - Use *Preview* tab to see what your *pull request* will actually look like
-->

### Before submitting a *pull request* make sure you have:
- [x] At least skimmed through [contributing guidelines](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#developer-instructions) including [yt-dlp coding conventions](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#yt-dlp-coding-conventions)
- [x] [Searched](https://github.com/yt-dlp/yt-dlp/search?q=is%3Apr&type=Issues) the bugtracker for similar pull requests

### In order to be accepted and merged into yt-dlp each piece of code must be in public domain or released under [Unlicense](http://unlicense.org/). Check those that apply and remove the others:
- [x] I am the original author of the code in this PR, and I am willing to release it under [Unlicense](http://unlicense.org/)
- [x] I am not the original author of the code in this PR, but it is in the public domain or released under [Unlicense](http://unlicense.org/) (provide reliable evidence) @bashonly 

### What is the purpose of your *pull request*? Check those that apply and remove the others:
- [x] Core bug fix/improvement

</details>



Patch:
diff --git a/yt_dlp/extractor/xinpianchang.py b/yt_dlp/extractor/xinpianchang.py
index 23ed9270d..a4263579a 100644
--- a/yt_dlp/extractor/xinpianchang.py
+++ b/yt_dlp/extractor/xinpianchang.py
@@ -45,7 +45,7 @@ class XinpianchangIE(InfoExtractor):
 
     def _real_extract(self, url):
         video_id = self._match_id(url)
-        webpage = self._download_webpage(url, video_id=video_id)
+        webpage = self._download_webpage(url, video_id=video_id, headers={'Referer': url})
         video_data = self._search_nextjs_data(webpage, video_id)['props']['pageProps']['detail']['video']
 
         data = self._download_json(
diff --git a/yt_dlp/jsinterp.py b/yt_dlp/jsinterp.py
index b59fb2c61..45aeffa22 100644
--- a/yt_dlp/jsinterp.py
+++ b/yt_dlp/jsinterp.py
@@ -590,6 +590,29 @@ def dict_item(key, val):
                     return ret, True
             return ret, False
 
+        m = re.match(fr'''(?x)
+                (?P<out>{_NAME_RE})(?:\[(?P<index>{_NESTED_BRACKETS})\])?\s*
+                (?P<op>{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS))})?
+                =(?!=)(?P<expr>.*)$
+            ''', expr)
+        if m:  # We are assigning a value to a variable
+            left_val = local_vars.get(m.group('out'))
+
+            if not m.group('index'):
+                local_vars[m.group('out')] = self._operator(
+                    m.group('op'), left_val, m.group('expr'), expr, local_vars, allow_recursion)
+                return local_vars[m.group('out')], should_return
+            elif left_val in (None, JS_Undefined):
+                raise self.Exception(f'Cannot index undefined variable {m.group("out")}', expr)
+
+            idx = self.interpret_expression(m.group('index'), local_vars, allow_recursion)
+            if not isinstance(idx, (int, float)):
+                raise self.Exception(f'List index {idx} must be integer', expr)
+            idx = int(idx)
+            left_val[idx] = self._operator(
+                m.group('op'), self._index(left_val, idx), m.group('expr'), expr, local_vars, allow_recursion)
+            return left_val[idx], should_return
+
         for m in re.finditer(rf'''(?x)
                 (?P<pre_sign>\+\+|--)(?P<var1>{_NAME_RE})|
                 (?P<var2>{_NAME_RE})(?P<post_sign>\+\+|--)''', expr):
@@ -606,11 +629,7 @@ def dict_item(key, val):
             return None, should_return
 
         m = re.match(fr'''(?x)
-            (?P<assign>
-                (?P<out>{_NAME_RE})(?:\[(?P<index>{_NESTED_BRACKETS})\])?\s*
-                (?P<op>{"|".join(map(re.escape, set(_OPERATORS) - _COMP_OPERATORS))})?
-                =(?!=)(?P<expr>.*)$
-            )|(?P<return>
+            (?P<return>
                 (?!if|return|true|false|null|undefined|NaN)(?P<name>{_NAME_RE})$
             )|(?P<attribute>
                 (?P<var>{_NAME_RE})(?:
@@ -622,25 +641,7 @@ def dict_item(key, val):
             )|(?P<function>
                 (?P<fname>{_NAME_RE})\((?P<args>.*)\)$
             )''', expr)
-        if m and m.group('assign'):
-            left_val = local_vars.get(m.group('out'))
-
-            if not m.group('index'):
-                local_vars[m.group('out')] = self._operator(
-                    m.group('op'), left_val, m.group('expr'), expr, local_vars, allow_recursion)
-                return local_vars[m.group('out')], should_return
-            elif left_val in (None, JS_Undefined):
-                raise self.Exception(f'Cannot index undefined variable {m.group("out")}', expr)
-
-            idx = self.interpret_expression(m.group('index'), local_vars, allow_recursion)
-            if not isinstance(idx, (int, float)):
-                raise self.Exception(f'List index {idx} must be integer', expr)
-            idx = int(idx)
-            left_val[idx] = self._operator(
-                m.group('op'), self._index(left_val, idx), m.group('expr'), expr, local_vars, allow_recursion)
-            return left_val[idx], should_return
-
-        elif expr.isdigit():
+        if expr.isdigit():
             return int(expr), should_return
 
         elif expr == 'break':


Test patch:
diff --git a/test/test_jsinterp.py b/test/test_jsinterp.py
index b14069ccc..2e3cdc2a5 100644
--- a/test/test_jsinterp.py
+++ b/test/test_jsinterp.py
@@ -478,6 +478,14 @@ def test_extract_function_with_global_stack(self):
         func = jsi.extract_function('c', {'e': 10}, {'f': 100, 'g': 1000})
         self.assertEqual(func([1]), 1111)
 
+    def test_increment_decrement(self):
+        self._test('function f() { var x = 1; return ++x; }', 2)
+        self._test('function f() { var x = 1; return x++; }', 1)
+        self._test('function f() { var x = 1; x--; return x }', 0)
+        self._test('function f() { var y; var x = 1; x++, --x, x--, x--, y="z", "abc", x++; return --x }', -1)
+        self._test('function f() { var a = "test--"; return a; }', 'test--')
+        self._test('function f() { var b = 1; var a = "b--"; return a; }', 'b--')
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/test/test_youtube_signature.py b/test/test_youtube_signature.py
index 0f0885366..3f777aed7 100644
--- a/test/test_youtube_signature.py
+++ b/test/test_youtube_signature.py
@@ -316,6 +316,10 @@
         'https://www.youtube.com/s/player/8a8ac953/tv-player-es6.vflset/tv-player-es6.js',
         'MiBYeXx_vRREbiCCmh', 'RtZYMVvmkE0JE',
     ),
+    (
+        'https://www.youtube.com/s/player/59b252b9/player_ias.vflset/en_US/base.js',
+        'D3XWVpYgwhLLKNK4AGX', 'aZrQ1qWJ5yv5h',
+    ),
 ]