Repository: yt-dlp/yt-dlp
Issue category: Bug Report

Issue:
[weverse] Failed to parse JSON (API end point might have changed)
### DO NOT REMOVE OR SKIP THE ISSUE TEMPLATE

- [X] I understand that I will be **blocked** if I *intentionally* remove or skip any mandatory\* field

### Checklist

- [X] I'm reporting that yt-dlp is broken on a **supported** site
- [X] I've verified that I have **updated yt-dlp to nightly or master** ([update instructions](https://github.com/yt-dlp/yt-dlp#update-channels))
- [X] I've checked that all provided URLs are playable in a browser with the same IP and same login details
- [X] I've checked that all URLs and arguments with special characters are [properly quoted or escaped](https://github.com/yt-dlp/yt-dlp/wiki/FAQ#video-url-contains-an-ampersand--and-im-getting-some-strange-output-1-2839-or-v-is-not-recognized-as-an-internal-or-external-command)
- [X] I've searched [known issues](https://github.com/yt-dlp/yt-dlp/issues/3766) and the [bugtracker](https://github.com/yt-dlp/yt-dlp/issues?q=) for similar issues **including closed ones**. DO NOT post duplicates
- [X] I've read the [guidelines for opening an issue](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#opening-an-issue)
- [x] I've read about [sharing account credentials](https://github.com/yt-dlp/yt-dlp/blob/master/CONTRIBUTING.md#are-you-willing-to-share-account-details-if-needed) and I'm willing to share it if required

### Region

all

### Provide a description that is worded well enough to be understood

starting from 20241010, the following error occurs for every live video (watchable on browser)

> ERROR: [Weverse] 3-181507553: 3-181507553: Failed to parse JSON (caused by JSONDecodeError("Expecting value in '': line 1 column 1 (char 0)"));

links to the video (non-membership, actually all video fails):

> https://weverse.io/fromis9/live/3-181507553

the JSON format or API end point might have changed ?

### Provide verbose output that clearly demonstrates the problem

- [X] Run **your** yt-dlp command with **-vU** flag added (`yt-dlp -vU <your command line>`)
- [x] If using API, add `'verbose': True` to `YoutubeDL` params instead
- [X] Copy the WHOLE output (starting with `[debug] Command-line config`) and insert it below

### Complete Verbose Output

```shell
[debug] Command-line config: ['-vU', '--', 'https://weverse.io/fromis9/live/3-181507553']
[debug] Encodings: locale cp950, fs utf-8, pref cp950, out utf-8, error utf-8, screen utf-8
[debug] yt-dlp version nightly@2024.10.07.232845 from yt-dlp/yt-dlp-nightly-builds [079a7bc33] (win_exe)
[debug] Python 3.8.10 (CPython AMD64 64bit) - Windows-10-10.0.19045-SP0 (OpenSSL 1.1.1k  25 Mar 2021)
[debug] exe versions: ffmpeg 2022-02-10-git-b6bb6b9f22-essentials_build-www.gyan.dev (setts), ffprobe 2022-02-10-git-b6bb6b9f22-essentials_build-www.gyan.dev
[debug] Optional libraries: Cryptodome-3.21.0, brotli-1.1.0, certifi-2024.08.30, curl_cffi-0.5.10, mutagen-1.47.0, requests-2.32.3, sqlite3-3.35.5, urllib3-2.2.3, websockets-13.1
[debug] Proxy map: {}
[debug] Request Handlers: urllib, requests, websockets, curl_cffi
[debug] Loaded 1838 extractors
[debug] Fetching release info: https://api.github.com/repos/yt-dlp/yt-dlp-nightly-builds/releases/latest
Latest version: nightly@2024.10.07.232845 from yt-dlp/yt-dlp-nightly-builds
yt-dlp is up to date (nightly@2024.10.07.232845 from yt-dlp/yt-dlp-nightly-builds)
[Weverse] Extracting URL: https://weverse.io/fromis9/live/3-181507553
[Weverse] 3-181507553: Downloading API JSON
[Weverse] 3-181507553: Downloading VOD API key
ERROR: [Weverse] 3-181507553: 3-181507553: Failed to parse JSON (caused by JSONDecodeError("Expecting value in '': line 1 column 1 (char 0)")); please report this issue on  https://github.com/yt-dlp/yt-dlp/issues?q= , filling out the appropriate issue template. Confirm you are on the latest version using  yt-dlp -U
  File "yt_dlp\extractor\common.py", line 741, in extract
  File "yt_dlp\extractor\weverse.py", line 304, in _real_extract
  File "yt_dlp\extractor\weverse.py", line 91, in _call_api
  File "yt_dlp\extractor\common.py", line 1151, in download_content
  File "yt_dlp\extractor\common.py", line 1118, in download_handle
  File "yt_dlp\extractor\common.py", line 1106, in parse
  File "yt_dlp\extractor\common.py", line 1093, in _parse_json
  File "yt_dlp\extractor\common.py", line 1076, in __print_error

  File "yt_dlp\utils\_utils.py", line 552, in decode
  File "json\decoder.py", line 337, in decode
  File "json\decoder.py", line 355, in raw_decode
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "yt_dlp\extractor\common.py", line 1090, in _parse_json
  File "json\__init__.py", line 370, in loads
  File "yt_dlp\utils\_utils.py", line 560, in decode
json.decoder.JSONDecodeError: Expecting value in '': line 1 column 1 (char 0)
```



Hints:

Patch:
diff --git a/yt_dlp/extractor/weverse.py b/yt_dlp/extractor/weverse.py
index c94ca9db9796..6f1a8b95d88d 100644
--- a/yt_dlp/extractor/weverse.py
+++ b/yt_dlp/extractor/weverse.py
@@ -27,8 +27,9 @@
 
 class WeverseBaseIE(InfoExtractor):
     _NETRC_MACHINE = 'weverse'
-    _ACCOUNT_API_BASE = 'https://accountapi.weverse.io/web/api/v2'
+    _ACCOUNT_API_BASE = 'https://accountapi.weverse.io/web/api'
     _API_HEADERS = {
+        'Accept': 'application/json',
         'Referer': 'https://weverse.io/',
         'WEV-device-Id': str(uuid.uuid4()),
     }
@@ -39,14 +40,14 @@ def _perform_login(self, username, password):
 
         headers = {
             'x-acc-app-secret': '5419526f1c624b38b10787e5c10b2a7a',
-            'x-acc-app-version': '2.2.6',
+            'x-acc-app-version': '3.3.6',
             'x-acc-language': 'en',
             'x-acc-service-id': 'weverse',
             'x-acc-trace-id': str(uuid.uuid4()),
             'x-clog-user-device-id': str(uuid.uuid4()),
         }
         valid_username = traverse_obj(self._download_json(
-            f'{self._ACCOUNT_API_BASE}/signup/email/status', None, note='Checking username',
+            f'{self._ACCOUNT_API_BASE}/v2/signup/email/status', None, note='Checking username',
             query={'email': username}, headers=headers, expected_status=(400, 404)), 'hasPassword')
         if not valid_username:
             raise ExtractorError('Invalid username provided', expected=True)
@@ -54,8 +55,9 @@ def _perform_login(self, username, password):
         headers['content-type'] = 'application/json'
         try:
             auth = self._download_json(
-                f'{self._ACCOUNT_API_BASE}/auth/token/by-credentials', None, data=json.dumps({
+                f'{self._ACCOUNT_API_BASE}/v3/auth/token/by-credentials', None, data=json.dumps({
                     'email': username,
+                    'otpSessionId': 'BY_PASS',
                     'password': password,
                 }, separators=(',', ':')).encode(), headers=headers, note='Logging in')
         except ExtractorError as e:
@@ -78,8 +80,10 @@ def _call_api(self, ep, video_id, data=None, note='Downloading API JSON'):
         # From https://ssl.pstatic.net/static/wevweb/2_3_2_11101725/public/static/js/main.e206f7c1.js:
         key = b'1b9cb6378d959b45714bec49971ade22e6e24e42'
         api_path = update_url_query(ep, {
+            # 'gcc': 'US',
             'appId': 'be4d79eb8fc7bd008ee82c8ec4ff6fd4',
             'language': 'en',
+            'os': 'WEB',
             'platform': 'WEB',
             'wpf': 'pc',
         })
@@ -152,7 +156,7 @@ def _parse_post_meta(self, metadata):
             'description': ((('extension', 'mediaInfo', 'body'), 'body'), {str}),
             'uploader': ('author', 'profileName', {str}),
             'uploader_id': ('author', 'memberId', {str}),
-            'creator': ('community', 'communityName', {str}),
+            'creators': ('community', 'communityName', {str}, all),
             'channel_id': (('community', 'author'), 'communityId', {str_or_none}),
             'duration': ('extension', 'video', 'playTime', {float_or_none}),
             'timestamp': ('publishedAt', {lambda x: int_or_none(x, 1000)}),
@@ -196,7 +200,7 @@ class WeverseIE(WeverseBaseIE):
             'channel': 'billlie',
             'channel_id': '72',
             'channel_url': 'https://weverse.io/billlie',
-            'creator': 'Billlie',
+            'creators': ['Billlie'],
             'timestamp': 1666262062,
             'upload_date': '20221020',
             'release_timestamp': 1666262058,
@@ -222,7 +226,7 @@ class WeverseIE(WeverseBaseIE):
             'channel': 'lesserafim',
             'channel_id': '47',
             'channel_url': 'https://weverse.io/lesserafim',
-            'creator': 'LE SSERAFIM',
+            'creators': ['LE SSERAFIM'],
             'timestamp': 1659353400,
             'upload_date': '20220801',
             'release_timestamp': 1659353400,
@@ -286,7 +290,7 @@ def _real_extract(self, url):
 
         elif live_status == 'is_live':
             video_info = self._call_api(
-                f'/video/v1.0/lives/{api_video_id}/playInfo?preview.format=json&preview.version=v2',
+                f'/video/v1.2/lives/{api_video_id}/playInfo?preview.format=json&preview.version=v2',
                 video_id, note='Downloading live JSON')
             playback = self._parse_json(video_info['lipPlayback'], video_id)
             m3u8_url = traverse_obj(playback, (
@@ -302,7 +306,7 @@ def _real_extract(self, url):
         else:
             infra_video_id = post['extension']['video']['infraVideoId']
             in_key = self._call_api(
-                f'/video/v1.0/vod/{api_video_id}/inKey?preview=false', video_id,
+                f'/video/v1.1/vod/{api_video_id}/inKey?preview=false', video_id,
                 data=b'{}', note='Downloading VOD API key')['inKey']
 
             video_info = self._download_json(
@@ -347,7 +351,6 @@ class WeverseMediaIE(WeverseBaseIE):
     _VALID_URL = r'https?://(?:www\.|m\.)?weverse\.io/(?P<artist>[^/?#]+)/media/(?P<id>[\d-]+)'
     _TESTS = [{
         'url': 'https://weverse.io/billlie/media/4-116372884',
-        'md5': '8efc9cfd61b2f25209eb1a5326314d28',
         'info_dict': {
             'id': 'e-C9wLSQs6o',
             'ext': 'mp4',
@@ -358,8 +361,9 @@ class WeverseMediaIE(WeverseBaseIE):
             'channel_url': 'https://www.youtube.com/channel/UCyc9sUCxELTDK9vELO5Fzeg',
             'uploader': 'Billlie',
             'uploader_id': '@Billlie',
-            'uploader_url': 'http://www.youtube.com/@Billlie',
+            'uploader_url': 'https://www.youtube.com/@Billlie',
             'upload_date': '20230403',
+            'timestamp': 1680533992,
             'duration': 211,
             'age_limit': 0,
             'playable_in_embed': True,
@@ -372,6 +376,8 @@ class WeverseMediaIE(WeverseBaseIE):
             'thumbnail': 'https://i.ytimg.com/vi/e-C9wLSQs6o/maxresdefault.jpg',
             'categories': ['Entertainment'],
             'tags': 'count:7',
+            'channel_is_verified': True,
+            'heatmap': 'count:100',
         },
     }, {
         'url': 'https://weverse.io/billlie/media/3-102914520',
@@ -386,7 +392,7 @@ class WeverseMediaIE(WeverseBaseIE):
             'channel': 'billlie',
             'channel_id': '72',
             'channel_url': 'https://weverse.io/billlie',
-            'creator': 'Billlie',
+            'creators': ['Billlie'],
             'timestamp': 1662174000,
             'upload_date': '20220903',
             'release_timestamp': 1662174000,
@@ -432,7 +438,7 @@ class WeverseMomentIE(WeverseBaseIE):
             'uploader_id': '66a07e164b56a696ee71c99315ffe27b',
             'channel': 'secretnumber',
             'channel_id': '56',
-            'creator': 'SECRET NUMBER',
+            'creators': ['SECRET NUMBER'],
             'duration': 10,
             'upload_date': '20230405',
             'timestamp': 1680653968,
@@ -441,7 +447,6 @@ class WeverseMomentIE(WeverseBaseIE):
             'comment_count': int,
             'availability': 'needs_auth',
         },
-        'skip': 'Moment has expired',
     }]
 
     def _real_extract(self, url):
@@ -571,7 +576,7 @@ class WeverseLiveIE(WeverseBaseIE):
             'channel': 'purplekiss',
             'channel_id': '35',
             'channel_url': 'https://weverse.io/purplekiss',
-            'creator': 'PURPLE KISS',
+            'creators': ['PURPLE KISS'],
             'timestamp': 1680780892,
             'upload_date': '20230406',
             'release_timestamp': 1680780883,
@@ -584,6 +589,31 @@ class WeverseLiveIE(WeverseBaseIE):
             'live_status': 'is_live',
         },
         'skip': 'Livestream has ended',
+    }, {
+        'url': 'https://weverse.io/lesserafim',
+        'info_dict': {
+            'id': '4-181521628',
+            'ext': 'mp4',
+            'title': r're:심심해서요',
+            'description': '',
+            'uploader': '채채🤎',
+            'uploader_id': 'd49b8b06f3cc1d92d655b25ab27ac2e7',
+            'channel': 'lesserafim',
+            'channel_id': '47',
+            'creators': ['LE SSERAFIM'],
+            'channel_url': 'https://weverse.io/lesserafim',
+            'timestamp': 1728570273,
+            'upload_date': '20241010',
+            'release_timestamp': 1728570264,
+            'release_date': '20241010',
+            'thumbnail': r're:https://phinf\.wevpstatic\.net/.+\.png',
+            'view_count': int,
+            'like_count': int,
+            'comment_count': int,
+            'availability': 'needs_auth',
+            'live_status': 'is_live',
+        },
+        'skip': 'Livestream has ended',
     }, {
         'url': 'https://weverse.io/billlie/',
         'only_matching': True,


Test patch: